Compare commits
25 Commits
nico/7-aug
...
nico/20-au
| Author | SHA1 | Date | |
|---|---|---|---|
| b580978f8e | |||
| 1c01397c0d | |||
| 90a6605efd | |||
| c22d865283 | |||
| 49067f0218 | |||
| d79425d529 | |||
| 4491d23bea | |||
| 1e154ced86 | |||
| bcc51aec12 | |||
| 8d15563f15 | |||
| d7a592c635 | |||
| 5e137ba658 | |||
| c99416c7f8 | |||
| 212e2db1fb | |||
| b8a45bc451 | |||
| 0777b00a7d | |||
| a035039b2c | |||
| a6832cad40 | |||
| a1d55e2b0a | |||
| c1583c21b1 | |||
| 2fe8b8ce1a | |||
| 5cbf7810bc | |||
| b3bf6b0327 | |||
| a65529cb23 | |||
| afc7bced44 |
10
prisma/data/ppid/ikm/jenis-kelamin/jenis-kelamin.json
Normal file
10
prisma/data/ppid/ikm/jenis-kelamin/jenis-kelamin.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cme8bt5o5000007lb9xp11unb",
|
||||||
|
"name": "Laki-laki"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8btctl000107lbh2hocgg8",
|
||||||
|
"name": "Perempuan"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cme8buup6000207lb54q9b0az",
|
||||||
|
"name": "Sangat Baik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8bv15o000307lbft9b0vzy",
|
||||||
|
"name": "Baik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8bvjvu000507lbgfsveog6",
|
||||||
|
"name": "Kurang Baik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8bvvm6000607lbh6rn2ubm",
|
||||||
|
"name": "Sangat Kurang Baik"
|
||||||
|
}
|
||||||
|
]
|
||||||
14
prisma/data/ppid/ikm/umur-responden/umur-responden.json
Normal file
14
prisma/data/ppid/ikm/umur-responden/umur-responden.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cme8bwgwu000707lbawc6fz3a",
|
||||||
|
"name": "Muda"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8hnx09000b07jl3ipifb1k",
|
||||||
|
"name": "Dewasa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cme8ho7dv000c07jlc7lr4b4w",
|
||||||
|
"name": "Lansia"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -50,23 +50,22 @@ model AppMenuChild {
|
|||||||
// ========================================= FILE STORAGE ========================================= //
|
// ========================================= FILE STORAGE ========================================= //
|
||||||
|
|
||||||
model FileStorage {
|
model FileStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
realName String
|
realName String
|
||||||
path String
|
path String
|
||||||
mimeType String
|
mimeType String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
link String
|
link String
|
||||||
category String // "image" / "document" / "other"
|
category String // "image" / "document" / "other"
|
||||||
Berita Berita[]
|
Berita Berita[]
|
||||||
PotensiDesa PotensiDesa[]
|
PotensiDesa PotensiDesa[]
|
||||||
Posyandu Posyandu[]
|
Posyandu Posyandu[]
|
||||||
StrukturPPID StrukturPPID[]
|
StrukturPPID StrukturPPID[]
|
||||||
GalleryFoto GalleryFoto[]
|
GalleryFoto GalleryFoto[]
|
||||||
|
|
||||||
Pelapor Pelapor[]
|
Pelapor Pelapor[]
|
||||||
Penghargaan Penghargaan[]
|
Penghargaan Penghargaan[]
|
||||||
ProfileDesaImage ProfileDesaImage[]
|
ProfileDesaImage ProfileDesaImage[]
|
||||||
@@ -98,10 +97,9 @@ model FileStorage {
|
|||||||
APBDesImage APBDes[] @relation("APBDesImage")
|
APBDesImage APBDes[] @relation("APBDesImage")
|
||||||
APBDesFile APBDes[] @relation("APBDesFile")
|
APBDesFile APBDes[] @relation("APBDesFile")
|
||||||
PrestasiDesa PrestasiDesa[]
|
PrestasiDesa PrestasiDesa[]
|
||||||
|
DataPerpustakaan DataPerpustakaan[]
|
||||||
DataPerpustakaan DataPerpustakaan[]
|
PegawaiPPID PegawaiPPID[]
|
||||||
|
PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[]
|
||||||
PegawaiPPID PegawaiPPID[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU LANDING PAGE ========================================= //
|
//========================================= MENU LANDING PAGE ========================================= //
|
||||||
@@ -221,6 +219,53 @@ model KategoriPrestasiDesa {
|
|||||||
PrestasiDesa PrestasiDesa[]
|
PrestasiDesa PrestasiDesa[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//========================================= INDEKS KEPUASAAN MASYARAKAT ========================================= //
|
||||||
|
model Responden {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
tanggal String // misal: 2025-05-01
|
||||||
|
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
|
||||||
|
jenisKelaminId String
|
||||||
|
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
|
||||||
|
ratingId String
|
||||||
|
kelompokUmur UmurResponden @relation(fields: [kelompokUmurId], references: [id])
|
||||||
|
kelompokUmurId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model JenisKelaminResponden {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Responden Responden[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model PilihanRatingResponden {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Responden Responden[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model UmurResponden {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Responden Responden[]
|
||||||
|
}
|
||||||
|
|
||||||
//========================================= MENU PPID ========================================= //
|
//========================================= MENU PPID ========================================= //
|
||||||
|
|
||||||
//========================================= STRUKTUR PPID ========================================= //
|
//========================================= STRUKTUR PPID ========================================= //
|
||||||
@@ -508,6 +553,19 @@ model ProfilPerbekel {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model PerbekelDariMasaKeMasa {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String @db.Text
|
||||||
|
periode String @db.Text
|
||||||
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String?
|
||||||
|
daerah String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================= BERITA ========================================= //
|
// ========================================= BERITA ========================================= //
|
||||||
model Berita {
|
model Berita {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -853,26 +911,56 @@ model PendaftaranJadwalKegiatan {
|
|||||||
|
|
||||||
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
|
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
|
||||||
model DataKematian_Kelahiran {
|
model DataKematian_Kelahiran {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
tahun String
|
kematian Kematian @relation(fields: [kematianId], references: [id])
|
||||||
kematianKasar String
|
kematianId String
|
||||||
kematianBayi String
|
kelahiran Kelahiran @relation(fields: [kelahiranId], references: [id])
|
||||||
kelahiranKasar String
|
kelahiranId 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Kelahiran {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
tanggal DateTime
|
||||||
|
jenisKelamin String
|
||||||
|
alamat String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
DataKematian_Kelahiran DataKematian_Kelahiran[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Kematian {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
tanggal DateTime
|
||||||
|
jenisKelamin String
|
||||||
|
alamat String
|
||||||
|
penyebab String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
DataKematian_Kelahiran DataKematian_Kelahiran[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= GRAFIK KEPUASAN ========================================= //
|
// ========================================= GRAFIK KEPUASAN ========================================= //
|
||||||
model GrafikKepuasan {
|
model GrafikKepuasan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
label String
|
nama String
|
||||||
jumlah String
|
tanggal DateTime
|
||||||
createdAt DateTime @default(now())
|
jenisKelamin String
|
||||||
updatedAt DateTime @updatedAt
|
alamat String
|
||||||
deletedAt DateTime @default(now())
|
penyakit String
|
||||||
isActive Boolean @default(true)
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= ARTIKEL KESEHATAN ========================================= //
|
// ========================================= ARTIKEL KESEHATAN ========================================= //
|
||||||
@@ -969,16 +1057,17 @@ model DoctorSign {
|
|||||||
|
|
||||||
// ========================================= POSYANDU ========================================= //
|
// ========================================= POSYANDU ========================================= //
|
||||||
model Posyandu {
|
model Posyandu {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
nomor String
|
nomor String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
jadwalPelayanan String
|
||||||
imageId String
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
imageId String
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
deletedAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
isActive Boolean @default(true)
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= PUSKESMAS ========================================= //
|
// ========================================= PUSKESMAS ========================================= //
|
||||||
|
|||||||
132
prisma/seed.ts
132
prisma/seed.ts
@@ -16,6 +16,9 @@ import potensi from "./data/list-potensi.json";
|
|||||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||||
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||||
|
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
||||||
|
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
||||||
|
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
||||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||||
@@ -122,8 +125,8 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("penghargaan success ...");
|
console.log("penghargaan success ...");
|
||||||
|
|
||||||
// =========== LAYANAN DESA ===========
|
// =========== LAYANAN DESA ===========
|
||||||
for (const p of pelayananSuratKeterangan) {
|
for (const p of pelayananSuratKeterangan) {
|
||||||
await prisma.pelayananSuratKeterangan.upsert({
|
await prisma.pelayananSuratKeterangan.upsert({
|
||||||
where: { id: p.id },
|
where: { id: p.id },
|
||||||
update: {
|
update: {
|
||||||
@@ -314,63 +317,42 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("visi misi desa success ...");
|
console.log("visi misi desa success ...");
|
||||||
|
|
||||||
// Flatten the nested array structure for posisiOrganisasiPPID
|
const flattenedPosisi = posisiOrganisasiPPID.flat();
|
||||||
const flattenedPosisiOrganisasiPPID = posisiOrganisasiPPID.flat();
|
|
||||||
|
// ✅ Urutkan berdasarkan hierarki
|
||||||
|
const sortedPosisi = flattenedPosisi.sort((a, b) => a.hierarki - b.hierarki);
|
||||||
|
|
||||||
|
for (const p of sortedPosisi) {
|
||||||
|
console.log(`Seeding: ${p.nama} (id: ${p.id}, parent: ${p.parentId})`);
|
||||||
|
|
||||||
|
if (p.parentId) {
|
||||||
|
const parentExists = flattenedPosisi.some((pos) => pos.id === p.parentId);
|
||||||
|
if (!parentExists) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Parent tidak ditemukan: ${p.parentId} untuk ${p.nama}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const p of flattenedPosisiOrganisasiPPID) {
|
|
||||||
await prisma.posisiOrganisasiPPID.upsert({
|
await prisma.posisiOrganisasiPPID.upsert({
|
||||||
where: {
|
where: { id: p.id },
|
||||||
id: p.id,
|
update: p,
|
||||||
},
|
create: p,
|
||||||
update: {
|
|
||||||
nama: p.nama,
|
|
||||||
deskripsi: p.deskripsi,
|
|
||||||
hierarki: p.hierarki,
|
|
||||||
parentId: p.parentId,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: p.id,
|
|
||||||
nama: p.nama,
|
|
||||||
deskripsi: p.deskripsi,
|
|
||||||
hierarki: p.hierarki,
|
|
||||||
parentId: p.parentId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("posisi organisasi success ...");
|
console.log("✅ Posisi organisasi berhasil");
|
||||||
|
|
||||||
// Flatten the nested array structure for pegawaiPPID
|
// 2. Seed Pegawai
|
||||||
const flattenedPegawaiPPID = pegawaiPPID.flat();
|
const flattenedPegawai = pegawaiPPID.flat();
|
||||||
|
for (const p of flattenedPegawai) {
|
||||||
for (const p of flattenedPegawaiPPID) {
|
|
||||||
await prisma.pegawaiPPID.upsert({
|
await prisma.pegawaiPPID.upsert({
|
||||||
where: {
|
where: { id: p.id },
|
||||||
id: p.id,
|
update: p,
|
||||||
},
|
create: p,
|
||||||
update: {
|
|
||||||
namaLengkap: p.namaLengkap,
|
|
||||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
|
||||||
email: p.email,
|
|
||||||
gelarAkademik: p.gelarAkademik,
|
|
||||||
telepon: p.telepon,
|
|
||||||
alamat: p.alamat,
|
|
||||||
posisiId: p.posisiId,
|
|
||||||
isActive: p.isActive,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: p.id,
|
|
||||||
namaLengkap: p.namaLengkap,
|
|
||||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
|
||||||
email: p.email,
|
|
||||||
gelarAkademik: p.gelarAkademik,
|
|
||||||
telepon: p.telepon,
|
|
||||||
alamat: p.alamat,
|
|
||||||
posisiId: p.posisiId,
|
|
||||||
isActive: p.isActive,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("pegawai success ...");
|
console.log("✅ Pegawai berhasil");
|
||||||
|
|
||||||
for (const l of pelayananPerizinanBerusaha) {
|
for (const l of pelayananPerizinanBerusaha) {
|
||||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||||
@@ -546,6 +528,54 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("visi misi PPID success ...");
|
console.log("visi misi PPID success ...");
|
||||||
|
|
||||||
|
for (const j of jenisKelamin) {
|
||||||
|
await prisma.jenisKelaminResponden.upsert({
|
||||||
|
where: {
|
||||||
|
id: j.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: j.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: j.id,
|
||||||
|
name: j.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("jenis kelamin responden success ...");
|
||||||
|
|
||||||
|
for (const r of pilihanRatingResponden) {
|
||||||
|
await prisma.pilihanRatingResponden.upsert({
|
||||||
|
where: {
|
||||||
|
id: r.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: r.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: r.id,
|
||||||
|
name: r.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("pilihan rating responden success ...");
|
||||||
|
|
||||||
|
for (const u of umurResponden) {
|
||||||
|
await prisma.umurResponden.upsert({
|
||||||
|
where: {
|
||||||
|
id: u.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: u.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: u.id,
|
||||||
|
name: u.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("umur responden success ...");
|
||||||
|
|
||||||
for (const v of dasarHukumPPID) {
|
for (const v of dasarHukumPPID) {
|
||||||
await prisma.dasarHukumPPID.upsert({
|
await prisma.dasarHukumPPID.upsert({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -58,6 +59,8 @@ const berita = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// State untuk berita utama (hanya 1)
|
||||||
|
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.BeritaGetPayload<{
|
| Prisma.BeritaGetPayload<{
|
||||||
@@ -70,38 +73,43 @@ const berita = proxy({
|
|||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
search: "",
|
||||||
async load(page = 1, limit = 10) {
|
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||||
berita.findMany.loading = true;
|
berita.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
berita.findMany.page = page;
|
berita.findMany.page = page;
|
||||||
try {
|
berita.findMany.search = search;
|
||||||
const res = await ApiFetch.api.desa.berita["find-many"].get({
|
|
||||||
query: {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (kategori) query.kategori = kategori;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa.berita["find-many"].get({ query });
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
berita.findMany.data = res.data.data ?? [];
|
berita.findMany.data = res.data.data ?? [];
|
||||||
berita.findMany.totalPages = res.data.totalPages ?? 1;
|
berita.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
berita.findMany.data = [];
|
||||||
|
berita.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Gagal fetch berita paginated:", err);
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
berita.findMany.data = [];
|
||||||
|
berita.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
berita.findMany.loading = false;
|
berita.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as
|
data: null as Prisma.BeritaGetPayload<{
|
||||||
| Prisma.BeritaGetPayload<{
|
include: {
|
||||||
include: {
|
image: true;
|
||||||
image: true;
|
kategoriBerita: true;
|
||||||
kategoriBerita: true;
|
};
|
||||||
};
|
}> | null,
|
||||||
}> | null,
|
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/desa/berita/${id}`);
|
const res = await fetch(`/api/desa/berita/${id}`);
|
||||||
@@ -109,11 +117,11 @@ const berita = proxy({
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
berita.findUnique.data = data.data ?? null;
|
berita.findUnique.data = data.data ?? null;
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to fetch berita:', res.statusText);
|
console.error("Failed to fetch berita:", res.statusText);
|
||||||
berita.findUnique.data = null;
|
berita.findUnique.data = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching berita:', error);
|
console.error("Error fetching berita:", error);
|
||||||
berita.findUnique.data = null;
|
berita.findUnique.data = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -127,14 +135,14 @@ const berita = proxy({
|
|||||||
berita.delete.loading = true;
|
berita.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/desa/berita/delete/${id}`, {
|
const response = await fetch(`/api/desa/berita/delete/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (response.ok && result?.success) {
|
if (response.ok && result?.success) {
|
||||||
toast.success(result.message || "Berita berhasil dihapus");
|
toast.success(result.message || "Berita berhasil dihapus");
|
||||||
await berita.findMany.load(); // refresh list
|
await berita.findMany.load(); // refresh list
|
||||||
@@ -159,21 +167,21 @@ const berita = proxy({
|
|||||||
toast.warn("ID tidak valid");
|
toast.warn("ID tidak valid");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/desa/berita/${id}`, {
|
const response = await fetch(`/api/desa/berita/${id}`, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
@@ -190,7 +198,9 @@ const berita = proxy({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading berita:", error);
|
console.error("Error loading berita:", error);
|
||||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -204,14 +214,14 @@ const berita = proxy({
|
|||||||
toast.error(err);
|
toast.error(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
berita.edit.loading = true;
|
berita.edit.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/desa/berita/${this.id}`, {
|
const response = await fetch(`/api/desa/berita/${this.id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
judul: this.form.judul,
|
judul: this.form.judul,
|
||||||
@@ -221,14 +231,16 @@ const berita = proxy({
|
|||||||
imageId: this.form.imageId,
|
imageId: this.form.imageId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success("Berhasil update berita");
|
toast.success("Berhasil update berita");
|
||||||
await berita.findMany.load(); // refresh list
|
await berita.findMany.load(); // refresh list
|
||||||
@@ -238,7 +250,11 @@ const berita = proxy({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating berita:", error);
|
console.error("Error updating berita:", error);
|
||||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update berita");
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update berita"
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
berita.edit.loading = false;
|
berita.edit.loading = false;
|
||||||
@@ -258,21 +274,22 @@ const berita = proxy({
|
|||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
// findFirst.load()
|
||||||
|
async load(kategori?: string) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await ApiFetch.api.desa.berita["find-first"].get();
|
const res = await ApiFetch.api.desa.berita["find-first"].get({
|
||||||
|
query: kategori ? { kategori } : {},
|
||||||
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
// Add type assertion to ensure type safety
|
this.data = res.data.data || null;
|
||||||
berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
|
} else {
|
||||||
include: {
|
this.data = null;
|
||||||
image: true;
|
|
||||||
kategoriBerita: true;
|
|
||||||
};
|
|
||||||
}> | null;
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Gagal fetch berita terbaru:", err);
|
console.error("Gagal fetch berita terbaru:", err);
|
||||||
|
this.data = null;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -286,7 +303,7 @@ const berita = proxy({
|
|||||||
};
|
};
|
||||||
}>[],
|
}>[],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -300,7 +317,7 @@ const berita = proxy({
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//=============== Kategori Berita ===============
|
//=============== Kategori Berita ===============
|
||||||
@@ -328,10 +345,9 @@ const kategoriBerita = proxy({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
kategoriBerita.create.loading = true;
|
kategoriBerita.create.loading = true;
|
||||||
const res =
|
const res = await ApiFetch.api.desa.kategoriberita["create"].post(
|
||||||
await ApiFetch.api.desa.kategoriberita[
|
kategoriBerita.create.form
|
||||||
"create"
|
);
|
||||||
].post(kategoriBerita.create.form);
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
kategoriBerita.findMany.load();
|
kategoriBerita.findMany.load();
|
||||||
return toast.success("Data Kategori Berita Berhasil Dibuat");
|
return toast.success("Data Kategori Berita Berhasil Dibuat");
|
||||||
@@ -354,10 +370,7 @@ const kategoriBerita = proxy({
|
|||||||
}>[],
|
}>[],
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
async load() {
|
||||||
const res =
|
const res = await ApiFetch.api.desa.kategoriberita["findMany"].get();
|
||||||
await ApiFetch.api.desa.kategoriberita[
|
|
||||||
"findMany"
|
|
||||||
].get();
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
kategoriBerita.findMany.data = res.data?.data ?? [];
|
kategoriBerita.findMany.data = res.data?.data ?? [];
|
||||||
}
|
}
|
||||||
@@ -372,9 +385,7 @@ const kategoriBerita = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(`/api/desa/kategoriberita/${id}`);
|
||||||
`/api/desa/kategoriberita/${id}`
|
|
||||||
);
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
kategoriBerita.findUnique.data = data.data ?? null;
|
kategoriBerita.findUnique.data = data.data ?? null;
|
||||||
@@ -396,15 +407,12 @@ const kategoriBerita = proxy({
|
|||||||
try {
|
try {
|
||||||
kategoriBerita.delete.loading = true;
|
kategoriBerita.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/kategoriberita/del/${id}`, {
|
||||||
`/api/desa/kategoriberita/del/${id}`,
|
method: "DELETE",
|
||||||
{
|
headers: {
|
||||||
method: "DELETE",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
@@ -414,7 +422,9 @@ const kategoriBerita = proxy({
|
|||||||
);
|
);
|
||||||
await kategoriBerita.findMany.load(); // refresh list
|
await kategoriBerita.findMany.load(); // refresh list
|
||||||
} else {
|
} else {
|
||||||
toast.error(result?.message || "Gagal menghapus Data Kategori Berita");
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus Data Kategori Berita"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal delete:", error);
|
console.error("Gagal delete:", error);
|
||||||
@@ -435,15 +445,12 @@ const kategoriBerita = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/kategoriberita/${id}`, {
|
||||||
`/api/desa/kategoriberita/${id}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -481,18 +488,15 @@ const kategoriBerita = proxy({
|
|||||||
try {
|
try {
|
||||||
kategoriBerita.update.loading = true;
|
kategoriBerita.update.loading = true;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/kategoriberita/${this.id}`, {
|
||||||
`/api/desa/kategoriberita/${this.id}`,
|
method: "PUT",
|
||||||
{
|
headers: {
|
||||||
method: "PUT",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
body: JSON.stringify({
|
||||||
},
|
name: this.form.name,
|
||||||
body: JSON.stringify({
|
}),
|
||||||
name: this.form.name,
|
});
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
@@ -508,7 +512,9 @@ const kategoriBerita = proxy({
|
|||||||
await kategoriBerita.findMany.load(); // refresh list
|
await kategoriBerita.findMany.load(); // refresh list
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "Gagal update data kategori berita");
|
throw new Error(
|
||||||
|
result.message || "Gagal update data kategori berita"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating data kategori berita:", error);
|
console.error("Error updating data kategori berita:", error);
|
||||||
@@ -529,7 +535,6 @@ const kategoriBerita = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 5. State global
|
// 5. State global
|
||||||
const stateDashboardBerita = proxy({
|
const stateDashboardBerita = proxy({
|
||||||
kategoriBerita,
|
kategoriBerita,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -68,10 +69,34 @@ const foto = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
foto.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
foto.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
foto.findMany.page = page;
|
||||||
|
foto.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
foto.findMany.data = res.data.data ?? [];
|
||||||
|
foto.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
foto.findMany.data = [];
|
||||||
|
foto.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch foto paginated:", err);
|
||||||
|
foto.findMany.data = [];
|
||||||
|
foto.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
foto.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -215,6 +240,28 @@ const foto = proxy({
|
|||||||
foto.update.form = { ...defaultFormFoto };
|
foto.update.form = { ...defaultFormFoto };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findRecent: {
|
||||||
|
data: [] as Prisma.GalleryFotoGetPayload<{
|
||||||
|
include: {
|
||||||
|
imageGalleryFoto: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.gallery.foto["find-recent"].get();
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
this.data = res.data.data ?? [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal fetch foto recent:", error);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const video = proxy({
|
const video = proxy({
|
||||||
@@ -257,10 +304,34 @@ const video = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
video.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
video.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
video.findMany.page = page;
|
||||||
|
video.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa.gallery.video["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
video.findMany.data = res.data.data ?? [];
|
||||||
|
video.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
video.findMany.data = [];
|
||||||
|
video.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch video paginated:", err);
|
||||||
|
video.findMany.data = [];
|
||||||
|
video.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
video.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,228 @@ import { toast } from "react-toastify";
|
|||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateKategoriPengumuman = z.object({
|
||||||
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultKategoriPengumuman = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const category = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultKategoriPengumuman },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateKategoriPengumuman.safeParse(category.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
category.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.kategoripengumuman["create"].post(
|
||||||
|
category.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
category.findMany.load();
|
||||||
|
return toast.success("Data Kategori Pengumuman Berhasil Dibuat");
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} finally {
|
||||||
|
category.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: [] as (Prisma.CategoryPengumumanGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> & {
|
||||||
|
_count: {
|
||||||
|
pengumumans: number;
|
||||||
|
};
|
||||||
|
})[],
|
||||||
|
loading: false,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
category.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.CategoryPengumumanGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/kategoripengumuman/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
category.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
category.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
category.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async delete(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
category.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/desa/kategoripengumuman/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "Data Kategori Pengumuman berhasil dihapus"
|
||||||
|
);
|
||||||
|
await category.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus Data Kategori Pengumuman"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menghapus Data Kategori Pengumuman"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
category.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultKategoriPengumuman },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/kategoripengumuman/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kategori pengumuman:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateKategoriPengumuman.safeParse(category.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
category.update.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/kategoripengumuman/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update data kategori pengumuman");
|
||||||
|
await category.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
result.message || "Gagal update data kategori pengumuman"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating data kategori pengumuman:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update data kategori pengumuman"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
category.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
category.update.id = "";
|
||||||
|
category.update.form = { ...defaultKategoriPengumuman };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const templateFormPengumuman = z.object({
|
const templateFormPengumuman = z.object({
|
||||||
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
@@ -12,33 +234,15 @@ const templateFormPengumuman = z.object({
|
|||||||
categoryPengumumanId: z.string().nonempty(),
|
categoryPengumumanId: z.string().nonempty(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const category = proxy({
|
const defaultForm = {
|
||||||
findMany: {
|
judul: "",
|
||||||
data: null as
|
deskripsi: "",
|
||||||
| null
|
content: "",
|
||||||
| Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[],
|
categoryPengumumanId: "",
|
||||||
async load() {
|
};
|
||||||
const res = await ApiFetch.api.desa.pengumuman.category[
|
|
||||||
"find-many"
|
|
||||||
].get();
|
|
||||||
if (res.status === 200) {
|
|
||||||
category.findMany.data = (res.data?.data as any) ?? [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type PengumumanForm = Prisma.PengumumanGetPayload<{
|
|
||||||
select: {
|
|
||||||
judul: true;
|
|
||||||
deskripsi: true;
|
|
||||||
content: true;
|
|
||||||
categoryPengumumanId: true;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
const pengumuman = proxy({
|
const pengumuman = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {} as PengumumanForm,
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templateFormPengumuman.safeParse(pengumuman.create.form);
|
const cek = templateFormPengumuman.safeParse(pengumuman.create.form);
|
||||||
@@ -74,11 +278,35 @@ const pengumuman = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
|
totalPages: 1,
|
||||||
console.log(res);
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
pengumuman.findMany.data = res.data?.data ?? [];
|
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||||
|
pengumuman.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
pengumuman.findMany.page = page;
|
||||||
|
pengumuman.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (kategori) query.kategori = kategori;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa.pengumuman["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pengumuman.findMany.data = res.data.data ?? [];
|
||||||
|
pengumuman.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
pengumuman.findMany.data = [];
|
||||||
|
pengumuman.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch pengumuman paginated:", err);
|
||||||
|
pengumuman.findMany.data = [];
|
||||||
|
pengumuman.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pengumuman.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -112,7 +340,7 @@ const pengumuman = proxy({
|
|||||||
try {
|
try {
|
||||||
pengumuman.delete.loading = true;
|
pengumuman.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/desa/pengumuman/delete/${id}`, {
|
const response = await fetch(`/api/desa/pengumuman/del/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -135,9 +363,9 @@ const pengumuman = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {
|
edit: {
|
||||||
id: "",
|
id: "",
|
||||||
form: {} as PengumumanForm,
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
@@ -153,6 +381,7 @@ const pengumuman = proxy({
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -168,20 +397,21 @@ const pengumuman = proxy({
|
|||||||
content: data.content,
|
content: data.content,
|
||||||
categoryPengumumanId: data.categoryPengumumanId || "",
|
categoryPengumumanId: data.categoryPengumumanId || "",
|
||||||
};
|
};
|
||||||
return data;
|
return data; // Return the loaded data
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result?.message || "Gagal mengambil data pengumuman");
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error((error as Error).message);
|
console.error("Error loading pengumuman:", error);
|
||||||
toast.error("Terjadi kesalahan saat mengambil data pengumuman");
|
toast.error(
|
||||||
} finally {
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
pengumuman.update.loading = false;
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const cek = templateFormPengumuman.safeParse(pengumuman.update.form);
|
const cek = templateFormPengumuman.safeParse(pengumuman.edit.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -191,7 +421,7 @@ const pengumuman = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pengumuman.update.loading = true;
|
pengumuman.edit.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/desa/pengumuman/${this.id}`, {
|
const response = await fetch(`/api/desa/pengumuman/${this.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -202,7 +432,7 @@ const pengumuman = proxy({
|
|||||||
judul: this.form.judul,
|
judul: this.form.judul,
|
||||||
deskripsi: this.form.deskripsi,
|
deskripsi: this.form.deskripsi,
|
||||||
content: this.form.content,
|
content: this.form.content,
|
||||||
categoryPengumumanId: this.form.categoryPengumumanId,
|
categoryPengumumanId: this.form.categoryPengumumanId || null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -231,9 +461,14 @@ const pengumuman = proxy({
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
pengumuman.update.loading = false;
|
pengumuman.edit.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
pengumuman.edit.id = "";
|
||||||
|
pengumuman.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
findFirst: {
|
findFirst: {
|
||||||
data: null as Prisma.PengumumanGetPayload<{
|
data: null as Prisma.PengumumanGetPayload<{
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
|
||||||
// ========================================= SEJARAH DESA ========================================= //
|
// ========================================= SEJARAH DESA ========================================= //
|
||||||
const sejarahDesaForm = z.object({
|
const sejarahDesaForm = z.object({
|
||||||
@@ -106,16 +108,13 @@ const sejarahDesa = proxy({
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/profile/sejarah/${this.id}`, {
|
||||||
`/api/desa/profile/sejarah/${this.id}`,
|
method: "PUT",
|
||||||
{
|
headers: {
|
||||||
method: "PUT",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
body: JSON.stringify(this.form),
|
||||||
},
|
});
|
||||||
body: JSON.stringify(this.form),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
@@ -409,16 +408,13 @@ const lambangDesa = proxy({
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/profile/lambang/${this.id}`, {
|
||||||
`/api/desa/profile/lambang/${this.id}`,
|
method: "PUT",
|
||||||
{
|
headers: {
|
||||||
method: "PUT",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
body: JSON.stringify(this.form),
|
||||||
},
|
});
|
||||||
body: JSON.stringify(this.form),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
@@ -588,14 +584,11 @@ const maskotDesa = proxy({
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/desa/profile/maskot/${this.id}`, {
|
||||||
`/api/desa/profile/maskot/${this.id}`,
|
method: "PUT",
|
||||||
{
|
headers: { "Content-Type": "application/json" },
|
||||||
method: "PUT",
|
body: JSON.stringify(this.form),
|
||||||
headers: { "Content-Type": "application/json" },
|
});
|
||||||
body: JSON.stringify(this.form),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
@@ -818,12 +811,247 @@ const profilPerbekel = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//========================================= MANTAN PERBEKEL ========================================= //
|
||||||
|
const mantanPerbekelForm = z.object({
|
||||||
|
nama: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
daerah: z.string().min(3, "Daerah minimal 3 karakter"),
|
||||||
|
periode: z.string().min(3, "Periode minimal 3 karakter"),
|
||||||
|
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mantanPerbekelDefaultForm = {
|
||||||
|
nama: "",
|
||||||
|
daerah: "",
|
||||||
|
periode: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mantanPerbekel = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...mantanPerbekelDefaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = mantanPerbekelForm.safeParse(mantanPerbekel.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mantanPerbekel.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.mantanperbekel["create"].post(
|
||||||
|
mantanPerbekel.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
mantanPerbekel.findMany.load();
|
||||||
|
return toast.success("Foto berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan foto");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
mantanPerbekel.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
mantanPerbekel.create.form = { ...mantanPerbekelDefaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PerbekelDariMasaKeMasaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
mantanPerbekel.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
mantanPerbekel.findMany.page = page;
|
||||||
|
mantanPerbekel.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa.mantanperbekel["findMany"].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
mantanPerbekel.findMany.data = res.data.data ?? [];
|
||||||
|
mantanPerbekel.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
mantanPerbekel.findMany.data = [];
|
||||||
|
mantanPerbekel.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch mantan perbekel paginated:", err);
|
||||||
|
mantanPerbekel.findMany.data = [];
|
||||||
|
mantanPerbekel.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
mantanPerbekel.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PerbekelDariMasaKeMasaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/mantanperbekel/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
mantanPerbekel.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch mantan perbekel:", res.statusText);
|
||||||
|
mantanPerbekel.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching mantan perbekel:", error);
|
||||||
|
mantanPerbekel.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
mantanPerbekel.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/mantanperbekel/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Mantan perbekel berhasil dihapus");
|
||||||
|
await mantanPerbekel.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus mantan perbekel");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus mantan perbekel");
|
||||||
|
} finally {
|
||||||
|
mantanPerbekel.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...mantanPerbekelDefaultForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/mantanperbekel/${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,
|
||||||
|
daerah: data.daerah,
|
||||||
|
periode: data.periode,
|
||||||
|
imageId: data.imageId || "",
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading foto:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = mantanPerbekelForm.safeParse(mantanPerbekel.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mantanPerbekel.update.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/mantanperbekel/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: this.form.nama,
|
||||||
|
daerah: this.form.daerah,
|
||||||
|
periode: this.form.periode,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
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 || "Mantan perbekel berhasil diupdate");
|
||||||
|
await mantanPerbekel.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate mantan perbekel");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating mantan perbekel:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Gagal mengupdate mantan perbekel"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
mantanPerbekel.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
mantanPerbekel.update.id = "";
|
||||||
|
mantanPerbekel.update.form = { ...mantanPerbekelDefaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const stateProfileDesa = proxy({
|
const stateProfileDesa = proxy({
|
||||||
lambangDesa,
|
lambangDesa,
|
||||||
maskotDesa,
|
maskotDesa,
|
||||||
profilPerbekel,
|
profilPerbekel,
|
||||||
visiMisiDesa,
|
visiMisiDesa,
|
||||||
sejarahDesa,
|
sejarahDesa,
|
||||||
|
mantanPerbekel,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stateProfileDesa;
|
export default stateProfileDesa;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.LowonganPekerjaanGetPayload<{
|
| Prisma.LowonganPekerjaanGetPayload<{
|
||||||
omit: { isActive: true };
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
lowonganKerjaState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
lowonganKerjaState.findMany.page = page;
|
||||||
|
lowonganKerjaState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
lowonganKerjaState.findMany.data = res.data.data ?? [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch lowongan kerja paginated:", err);
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
lowonganKerjaState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -53,22 +54,47 @@ const pasarDesa = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<
|
data: null as
|
||||||
Prisma.PasarDesaGetPayload<{
|
| Prisma.PasarDesaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
KategoriToPasar: {
|
KategoriToPasar: {
|
||||||
include: {
|
include: {
|
||||||
kategori: true;
|
kategori: true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
}>[]
|
||||||
}>
|
| null,
|
||||||
> | null,
|
page: 1,
|
||||||
async load() {
|
totalPages: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
pasarDesa.findMany.data = res.data?.data ?? [];
|
load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
|
||||||
|
pasarDesa.findMany.loading = true;
|
||||||
|
pasarDesa.findMany.page = page;
|
||||||
|
pasarDesa.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (categoryId) query.categoryId = categoryId;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pasarDesa.findMany.data = res.data.data ?? [];
|
||||||
|
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
pasarDesa.findMany.data = [];
|
||||||
|
pasarDesa.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch keamanan lingkungan paginated:", err);
|
||||||
|
pasarDesa.findMany.data = [];
|
||||||
|
pasarDesa.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pasarDesa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -272,14 +298,41 @@ const kategoriProduk = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<{
|
data: null as
|
||||||
id: string;
|
| Prisma.KategoriProdukGetPayload<{
|
||||||
nama: string;
|
omit: {
|
||||||
}> | null,
|
isActive: true;
|
||||||
async load() {
|
};
|
||||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
|
}>[]
|
||||||
if (res.status === 200) {
|
| null,
|
||||||
kategoriProduk.findMany.data = res.data?.data ?? [];
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search2: "",
|
||||||
|
load: async (page = 1, limit = 10, search2 = "") => {
|
||||||
|
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kategoriProduk.findMany.page = page;
|
||||||
|
kategoriProduk.findMany.search2 = search2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search2) query.search2 = search2;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kategoriProduk.findMany.data = res.data.data ?? [];
|
||||||
|
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kategoriProduk.findMany.data = [];
|
||||||
|
kategoriProduk.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kategori produk paginated:", err);
|
||||||
|
kategoriProduk.findMany.data = [];
|
||||||
|
kategoriProduk.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kategoriProduk.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -53,15 +54,39 @@ const keamananLingkunganState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.KeamananLingkunganGetPayload<{
|
| Prisma.KeamananLingkunganGetPayload<{
|
||||||
include: { image: true };
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.keamananlingkungan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
keamananLingkunganState.findMany.data = res.data?.data ?? [];
|
keamananLingkunganState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
keamananLingkunganState.findMany.page = page;
|
||||||
|
keamananLingkunganState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.keamananlingkungan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
keamananLingkunganState.findMany.data = res.data.data ?? [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
keamananLingkunganState.findMany.data = [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch keamanan lingkungan paginated:", err);
|
||||||
|
keamananLingkunganState.findMany.data = [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
keamananLingkunganState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -63,13 +64,41 @@ const polsekTerdekatState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.PolsekTerdekatGetPayload<{
|
| Prisma.PolsekTerdekatGetPayload<{
|
||||||
include: { layananPolsek: true };
|
include: {
|
||||||
|
layananPolsek: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
polsekTerdekatState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
polsekTerdekatState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
polsekTerdekatState.findMany.page = page;
|
||||||
|
polsekTerdekatState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get(
|
||||||
|
{ query }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
polsekTerdekatState.findMany.data = res.data.data ?? [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
polsekTerdekatState.findMany.data = [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch polsek terdekat paginated:", err);
|
||||||
|
polsekTerdekatState.findMany.data = [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
polsekTerdekatState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -237,6 +266,29 @@ const polsekTerdekatState = proxy({
|
|||||||
polsekTerdekatState.edit.form = { ...defaultForm };
|
polsekTerdekatState.edit.form = { ...defaultForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findFirst: {
|
||||||
|
data: null as Prisma.PolsekTerdekatGetPayload<{
|
||||||
|
include: {
|
||||||
|
layananPolsek: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
load: async () => { // Changed to arrow function
|
||||||
|
polsekTerdekatState.findFirst.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.keamanan.polsekterdekat["find-first"].get();
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
polsekTerdekatState.findFirst.data = res.data.data || null;
|
||||||
|
} else {
|
||||||
|
polsekTerdekatState.findFirst.data = null;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch polsek terdekat terbaru:", err);
|
||||||
|
} finally {
|
||||||
|
polsekTerdekatState.findFirst.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default polsekTerdekatState;
|
export default polsekTerdekatState;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -53,15 +54,39 @@ const tipsKeamananState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.MenuTipsKeamananGetPayload<{
|
| Prisma.MenuTipsKeamananGetPayload<{
|
||||||
include: { image: true };
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.menutipskeamanan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
tipsKeamananState.findMany.data = res.data?.data ?? [];
|
tipsKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
tipsKeamananState.findMany.page = page;
|
||||||
|
tipsKeamananState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.menutipskeamanan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
tipsKeamananState.findMany.data = res.data.data ?? [];
|
||||||
|
tipsKeamananState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch menu tips keamanan paginated:", err);
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
tipsKeamananState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { toast } from "react-toastify";
|
|||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
//fasilitas kesehatan aja
|
||||||
// Validasi form
|
// Validasi form
|
||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
name: z.string().min(1, "Nama harus diisi"),
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
@@ -61,7 +62,7 @@ const defaultForm = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const fasilitasKesehatanState = proxy({
|
const fasilitasKesehatan = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: { ...defaultForm },
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -86,7 +87,7 @@ const fasilitasKesehatanState = proxy({
|
|||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success("Berhasil menambahkan fasilitas kesehatan");
|
toast.success("Berhasil menambahkan fasilitas kesehatan");
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
await fasilitasKesehatanState.findMany.load();
|
await fasilitasKesehatan.findMany.load();
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -102,7 +103,6 @@ const fasilitasKesehatanState = proxy({
|
|||||||
this.form = { ...defaultForm };
|
this.form = { ...defaultForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.FasilitasKesehatanGetPayload<{
|
| Prisma.FasilitasKesehatanGetPayload<{
|
||||||
@@ -156,7 +156,7 @@ const fasilitasKesehatanState = proxy({
|
|||||||
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
|
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
fasilitasKesehatanState.findUnique.data = data.data ?? null;
|
fasilitasKesehatan.findUnique.data = data.data ?? null;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal load data fasilitas kesehatan");
|
toast.error("Gagal load data fasilitas kesehatan");
|
||||||
}
|
}
|
||||||
@@ -176,8 +176,8 @@ const fasilitasKesehatanState = proxy({
|
|||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
|
|
||||||
fasilitasKesehatanState.edit.id = data.id;
|
fasilitasKesehatan.edit.id = data.id;
|
||||||
fasilitasKesehatanState.edit.form = {
|
fasilitasKesehatan.edit.form = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
informasiUmum: {
|
informasiUmum: {
|
||||||
fasilitas: data.informasiumum.fasilitas,
|
fasilitas: data.informasiumum.fasilitas,
|
||||||
@@ -205,7 +205,7 @@ const fasilitasKesehatanState = proxy({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form);
|
const cek = templateForm.safeParse(fasilitasKesehatan.edit.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const errMsg = cek.error.issues
|
const errMsg = cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}: ${v.message}`)
|
.map((v) => `${v.path.join(".")}: ${v.message}`)
|
||||||
@@ -215,42 +215,38 @@ const fasilitasKesehatanState = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fasilitasKesehatanState.edit.loading = true;
|
fasilitasKesehatan.edit.loading = true;
|
||||||
const payload = {
|
const payload = {
|
||||||
name: fasilitasKesehatanState.edit.form.name,
|
name: fasilitasKesehatan.edit.form.name,
|
||||||
informasiUmum: {
|
informasiUmum: {
|
||||||
fasilitas:
|
fasilitas: fasilitasKesehatan.edit.form.informasiUmum.fasilitas,
|
||||||
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas,
|
alamat: fasilitasKesehatan.edit.form.informasiUmum.alamat,
|
||||||
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
|
|
||||||
jamOperasional:
|
jamOperasional:
|
||||||
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional,
|
fasilitasKesehatan.edit.form.informasiUmum.jamOperasional,
|
||||||
},
|
},
|
||||||
layananUnggulan: {
|
layananUnggulan: {
|
||||||
content: fasilitasKesehatanState.edit.form.layananUnggulan.content,
|
content: fasilitasKesehatan.edit.form.layananUnggulan.content,
|
||||||
},
|
},
|
||||||
dokterdanTenagaMedis: {
|
dokterdanTenagaMedis: {
|
||||||
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name,
|
name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name,
|
||||||
specialist:
|
specialist:
|
||||||
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist,
|
fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
|
||||||
jadwal:
|
jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
|
||||||
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
|
|
||||||
},
|
},
|
||||||
fasilitasPendukung: {
|
fasilitasPendukung: {
|
||||||
content:
|
content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
|
||||||
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
|
|
||||||
},
|
},
|
||||||
prosedurPendaftaran: {
|
prosedurPendaftaran: {
|
||||||
content:
|
content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
|
||||||
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
|
|
||||||
},
|
},
|
||||||
tarifDanLayanan: {
|
tarifDanLayanan: {
|
||||||
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan,
|
layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
|
||||||
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif,
|
tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`,
|
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatan.edit.id}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -264,7 +260,7 @@ const fasilitasKesehatanState = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Berhasil update fasilitas kesehatan");
|
toast.success("Berhasil update fasilitas kesehatan");
|
||||||
await fasilitasKesehatanState.findMany.load();
|
await fasilitasKesehatan.findMany.load();
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -272,37 +268,297 @@ const fasilitasKesehatanState = proxy({
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
fasilitasKesehatanState.edit.loading = false;
|
fasilitasKesehatan.edit.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
fasilitasKesehatanState.edit.id = "";
|
fasilitasKesehatan.edit.id = "";
|
||||||
fasilitasKesehatanState.edit.form = { ...defaultForm };
|
fasilitasKesehatan.edit.form = { ...defaultForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId(id: string){
|
async byId(id: string) {
|
||||||
try {
|
try {
|
||||||
fasilitasKesehatanState.delete.loading = true;
|
fasilitasKesehatan.delete.loading = true;
|
||||||
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, {
|
const res = await fetch(
|
||||||
method: "DELETE",
|
`/api/kesehatan/fasilitas-kesehatan/del/${id}`,
|
||||||
});
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
if (res.ok && result.success) {
|
if (res.ok && result.success) {
|
||||||
toast.success("Fasilitas kesehatan berhasil dihapus");
|
toast.success("Fasilitas kesehatan berhasil dihapus");
|
||||||
await fasilitasKesehatanState.findMany.load();
|
await fasilitasKesehatan.findMany.load();
|
||||||
} else {
|
} else {
|
||||||
toast.error(result.message || "Gagal menghapus");
|
toast.error(result.message || "Gagal menghapus");
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("Terjadi kesalahan saat menghapus");
|
toast.error("Terjadi kesalahan saat menghapus");
|
||||||
} finally {
|
} finally {
|
||||||
fasilitasKesehatanState.delete.loading = false;
|
fasilitasKesehatan.delete.loading = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//dokter & tenaga medis
|
||||||
|
const templateDokterForm = z.object({
|
||||||
|
name: z.string().min(1, "Nama tidak boleh kosong"),
|
||||||
|
specialist: z.string().min(1, "Spesialis tidak boleh kosong"),
|
||||||
|
jadwal: z.string().min(1, "Jadwal tidak boleh kosong"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultDokterForm = {
|
||||||
|
name: "",
|
||||||
|
specialist: "",
|
||||||
|
jadwal: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dokter = proxy({
|
||||||
|
create: {
|
||||||
|
create: {
|
||||||
|
form: defaultDokterForm,
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateDokterForm.safeParse(dokter.create.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dokter.create.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
|
||||||
|
"create"
|
||||||
|
].post(dokter.create.create.form);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
const id = res.data?.data;
|
||||||
|
if (id) {
|
||||||
|
toast.success("Success create");
|
||||||
|
dokter.create.create.form = { ...defaultDokterForm };
|
||||||
|
dokter.findMany.load();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toast.error("failed create");
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
dokter.create.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.DokterdanTenagaMedisGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
dokter.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
dokter.findMany.page = page;
|
||||||
|
dokter.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
|
||||||
|
"findMany"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
dokter.findMany.data = res.data.data ?? [];
|
||||||
|
dokter.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
dokter.findMany.data = [];
|
||||||
|
dokter.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch dokter tenaga medis paginated:", err);
|
||||||
|
dokter.findMany.data = [];
|
||||||
|
dokter.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
dokter.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.DokterdanTenagaMedisGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/doktertenagamedis/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
dokter.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to fetch dokter dan tenaga medis",
|
||||||
|
res.statusText
|
||||||
|
);
|
||||||
|
dokter.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching dokter dan tenaga medis", error);
|
||||||
|
dokter.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultDokterForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/doktertenagamedis/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
specialist: data.specialist,
|
||||||
|
jadwal: data.jadwal,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading dokter dan tenaga medis:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
name: this.form.name,
|
||||||
|
specialist: this.form.specialist,
|
||||||
|
jadwal: this.form.jadwal,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cek = templateDokterForm.safeParse(formData);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const res = await fetch(`/api/kesehatan/doktertenagamedis/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await dokter.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update error:", error);
|
||||||
|
toast.error("Gagal update data dokter dan tenaga medis");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
return toast.warn("ID tidak valid");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dokter.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/kesehatan/doktertenagamedis/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "Dokter dan tenaga medis berhasil dihapus"
|
||||||
|
);
|
||||||
|
await dokter.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus dokter dan tenaga medis"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus dokter dan tenaga medis");
|
||||||
|
} finally {
|
||||||
|
dokter.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fasilitasKesehatanState = proxy({
|
||||||
|
fasilitasKesehatan,
|
||||||
|
dokter
|
||||||
|
});
|
||||||
|
|
||||||
export default fasilitasKesehatanState;
|
export default fasilitasKesehatanState;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -5,20 +6,19 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateGrafikKepuasan = z.object({
|
const templateGrafikKepuasan = z.object({
|
||||||
label: z.string().min(2, "Label harus diisi"),
|
nama: z.string().min(2, "Nama harus diisi"),
|
||||||
jumlah: z.string().min(1, "Jumlah harus diisi"),
|
tanggal: z.string().min(1, "Tanggal harus diisi"),
|
||||||
|
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||||
|
alamat: z.string().min(1, "Alamat harus diisi"),
|
||||||
|
penyakit: z.string().min(1, "Penyakit harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
|
const defaultForm = {
|
||||||
select: {
|
nama: "",
|
||||||
label: true;
|
tanggal: "",
|
||||||
jumlah: true;
|
jenisKelamin: "",
|
||||||
};
|
alamat: "",
|
||||||
}>;
|
penyakit: "",
|
||||||
|
|
||||||
const defaultForm: GrafikKepuasan = {
|
|
||||||
label: "",
|
|
||||||
jumlah: ""
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const grafikkepuasan = proxy({
|
const grafikkepuasan = proxy({
|
||||||
@@ -36,16 +36,15 @@ const grafikkepuasan = proxy({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
grafikkepuasan.create.loading = true;
|
grafikkepuasan.create.loading = true;
|
||||||
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(grafikkepuasan.create.form);
|
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
|
||||||
|
grafikkepuasan.create.form
|
||||||
|
);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const id = res.data?.data?.id;
|
const id = res.data?.data;
|
||||||
if (id) {
|
if (id) {
|
||||||
toast.success("Success create");
|
toast.success("Success create");
|
||||||
grafikkepuasan.create.form = {
|
grafikkepuasan.create.form = { ...defaultForm };
|
||||||
label: "",
|
|
||||||
jumlah: "",
|
|
||||||
};
|
|
||||||
grafikkepuasan.findMany.load();
|
grafikkepuasan.findMany.load();
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -62,21 +61,49 @@ const grafikkepuasan = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[]
|
| Prisma.GrafikKepuasanGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
grafikkepuasan.findMany.data = res.data?.data ?? [];
|
grafikkepuasan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
grafikkepuasan.findMany.page = page;
|
||||||
|
grafikkepuasan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
grafikkepuasan.findMany.data = res.data.data ?? [];
|
||||||
|
grafikkepuasan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
grafikkepuasan.findMany.data = [];
|
||||||
|
grafikkepuasan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
grafikkepuasan.findMany.data = [];
|
||||||
|
grafikkepuasan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
grafikkepuasan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.GrafikKepuasanGetPayload<{
|
data: null as Prisma.GrafikKepuasanGetPayload<{
|
||||||
omit: { isActive: true }
|
omit: { isActive: true };
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
|
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
|
||||||
@@ -95,88 +122,137 @@ const grafikkepuasan = proxy({
|
|||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
id: "",
|
id: "",
|
||||||
form: {...defaultForm},
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId() {
|
async load(id: string) {
|
||||||
},
|
if (!id) {
|
||||||
async submit() {
|
toast.warn("ID tidak valid");
|
||||||
const id = this.id;
|
return null;
|
||||||
if (!id) {
|
|
||||||
toast.warn("ID tidak valid");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.update.form);
|
|
||||||
if (!cek.success) {
|
|
||||||
const err = `[${cek.error.issues
|
|
||||||
.map((v) => `${v.path.join(".")}`)
|
|
||||||
.join("\n")}] required`;
|
|
||||||
return toast.error(err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.form),
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok || !result?.success) {
|
|
||||||
throw new Error(result?.message || "Gagal update data");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Berhasil update data!");
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
|
if (!response.ok) {
|
||||||
await grafikkepuasan.findMany.load();
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
return result.data;
|
const result = await response.json();
|
||||||
} catch (error) {
|
|
||||||
console.error("Error update data:", error);
|
|
||||||
toast.error("Gagal update data grafik kepuasan");
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
loading: false,
|
|
||||||
async byId(id: string) {
|
|
||||||
if (!id) {
|
|
||||||
return toast.warn("ID tidak valid");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
grafikkepuasan.delete.loading = true;
|
|
||||||
|
|
||||||
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
|
if (result?.success) {
|
||||||
method: "DELETE",
|
const data = result.data;
|
||||||
headers: {
|
this.id = data.id;
|
||||||
"Content-Type": "application/json",
|
this.form = {
|
||||||
},
|
nama: data.nama,
|
||||||
});
|
tanggal: data.tanggal,
|
||||||
|
jenisKelamin: data.jenisKelamin,
|
||||||
const result = await response.json();
|
alamat: data.alamat,
|
||||||
|
penyakit: data.penyakit,
|
||||||
if (response.ok && result?.success) {
|
};
|
||||||
toast.success(
|
return data; // Return the loaded data
|
||||||
result.message || "Grafik kepuasan berhasil dihapus"
|
} else {
|
||||||
);
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
await grafikkepuasan.findMany.load(); // refresh list
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik kepuasan:", error);
|
||||||
toast.error(
|
toast.error(
|
||||||
result?.message || "Gagal menghapus grafik kepuasan"
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
);
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error("Gagal delete:", error);
|
async submit() {
|
||||||
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
|
const id = this.id;
|
||||||
} finally {
|
if (!id) {
|
||||||
grafikkepuasan.delete.loading = false;
|
toast.warn("ID tidak valid");
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const formData = {
|
||||||
|
nama: this.form.nama,
|
||||||
|
tanggal: this.form.tanggal,
|
||||||
|
jenisKelamin: this.form.jenisKelamin,
|
||||||
|
alamat: this.form.alamat,
|
||||||
|
penyakit: this.form.penyakit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cek = templateGrafikKepuasan.safeParse(formData);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await grafikkepuasan.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update error:", error);
|
||||||
|
toast.error("Gagal update data grafik kepuasan");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
return toast.warn("ID tidak valid");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
grafikkepuasan.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/kesehatan/grafikkepuasan/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Grafik kepuasan berhasil dihapus");
|
||||||
|
await grafikkepuasan.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus grafik kepuasan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
|
||||||
|
} finally {
|
||||||
|
grafikkepuasan.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default grafikkepuasan;
|
export default grafikkepuasan;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templatePersentase = z.object({
|
//persentase kelahiran kematian
|
||||||
|
|
||||||
|
const templatePersentaseKelahiran = z.object({
|
||||||
tahun: z.string().min(4, "Tahun harus diisi"),
|
tahun: z.string().min(4, "Tahun harus diisi"),
|
||||||
kematianKasar: z.string().min(1, "Kematian kasar harus diisi"),
|
kematianKasar: z.string().min(1, "Kematian kasar harus diisi"),
|
||||||
kelahiranKasar: z.string().min(1, "Kelahiran kasar harus diisi"),
|
kelahiranKasar: z.string().min(1, "Kelahiran kasar harus diisi"),
|
||||||
@@ -13,18 +16,14 @@ const templatePersentase = z.object({
|
|||||||
|
|
||||||
type Persentase = Prisma.DataKematian_KelahiranGetPayload<{
|
type Persentase = Prisma.DataKematian_KelahiranGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
tahun: true;
|
kematianId: true;
|
||||||
kematianKasar: true;
|
kelahiranId: true;
|
||||||
kelahiranKasar: true;
|
|
||||||
kematianBayi: true;
|
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const defaultForm: Persentase = {
|
const defaultForm: Persentase = {
|
||||||
tahun: "",
|
kematianId: "",
|
||||||
kematianKasar: "",
|
kelahiranId: "",
|
||||||
kelahiranKasar: "",
|
|
||||||
kematianBayi: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const persentasekelahiran = proxy({
|
const persentasekelahiran = proxy({
|
||||||
@@ -32,7 +31,9 @@ const persentasekelahiran = proxy({
|
|||||||
form: defaultForm,
|
form: defaultForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templatePersentase.safeParse(persentasekelahiran.create.form);
|
const cek = templatePersentaseKelahiran.safeParse(
|
||||||
|
persentasekelahiran.create.form
|
||||||
|
);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -47,7 +48,7 @@ const persentasekelahiran = proxy({
|
|||||||
].post(persentasekelahiran.create.form);
|
].post(persentasekelahiran.create.form);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const id = res.data?.data?.id;
|
const id = res.data?.data;
|
||||||
if (id) {
|
if (id) {
|
||||||
toast.success("Success create");
|
toast.success("Success create");
|
||||||
persentasekelahiran.create.form = { ...defaultForm };
|
persentasekelahiran.create.form = { ...defaultForm };
|
||||||
@@ -69,21 +70,51 @@ const persentasekelahiran = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.DataKematian_KelahiranGetPayload<{
|
| Prisma.DataKematian_KelahiranGetPayload<{
|
||||||
omit: { isActive: true };
|
include: {
|
||||||
|
kematian: true;
|
||||||
|
kelahiran: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
persentasekelahiran.findMany.data = res.data?.data ?? [];
|
persentasekelahiran.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
persentasekelahiran.findMany.page = page;
|
||||||
|
persentasekelahiran.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
persentasekelahiran.findMany.data = res.data.data ?? [];
|
||||||
|
persentasekelahiran.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
persentasekelahiran.findMany.data = [];
|
||||||
|
persentasekelahiran.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
persentasekelahiran.findMany.data = [];
|
||||||
|
persentasekelahiran.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
persentasekelahiran.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.DataKematian_KelahiranGetPayload<{
|
data: null as Prisma.DataKematian_KelahiranGetPayload<{
|
||||||
omit: { isActive: true };
|
include: {
|
||||||
|
kematian: true;
|
||||||
|
kelahiran: true;
|
||||||
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
@@ -114,13 +145,11 @@ const persentasekelahiran = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
tahun: this.form.tahun,
|
kematianId: this.form.kematianId,
|
||||||
kematianKasar: this.form.kematianKasar,
|
kelahiranId: this.form.kelahiranId,
|
||||||
kelahiranKasar: this.form.kelahiranKasar,
|
|
||||||
kematianBayi: this.form.kematianBayi,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cek = templatePersentase.safeParse(formData);
|
const cek = templatePersentaseKelahiran.safeParse(formData);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -197,4 +226,521 @@ const persentasekelahiran = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default persentasekelahiran;
|
// data kelahiran
|
||||||
|
|
||||||
|
const templateKelahiran = z.object({
|
||||||
|
nama: z.string().min(1, "Nama harus diisi"),
|
||||||
|
tanggal: z.string().min(4, "Tahun harus diisi"),
|
||||||
|
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||||
|
alamat: z.string().min(1, "Alamat harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultKelahiran = {
|
||||||
|
nama: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelamin: "",
|
||||||
|
alamat: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const kelahiran = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultKelahiran }, // ✅ ini kunci fix-nya
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateKelahiran.safeParse(kelahiran.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kelahiran.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.kelahiran["create"].post(
|
||||||
|
kelahiran.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
kelahiran.findMany.load();
|
||||||
|
return toast.success("Kelahiran berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan kelahiran");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
kelahiran.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
kelahiran.create.form = { ...defaultKelahiran };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.KelahiranGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
kelahiran.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kelahiran.findMany.page = page;
|
||||||
|
kelahiran.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.kelahiran["findMany"].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kelahiran.findMany.data = res.data.data ?? [];
|
||||||
|
kelahiran.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kelahiran.findMany.data = [];
|
||||||
|
kelahiran.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kelahiran paginated:", err);
|
||||||
|
kelahiran.findMany.data = [];
|
||||||
|
kelahiran.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kelahiran.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KelahiranGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/kelahiran/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kelahiran.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch kelahiran:", res.statusText);
|
||||||
|
kelahiran.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching kelahiran:", error);
|
||||||
|
kelahiran.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
kelahiran.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/kelahiran/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Kelahiran berhasil dihapus");
|
||||||
|
await kelahiran.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus kelahiran");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kelahiran");
|
||||||
|
} finally {
|
||||||
|
kelahiran.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultKelahiran },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/kelahiran/${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,
|
||||||
|
tanggal: data.tanggal,
|
||||||
|
jenisKelamin: data.jenisKelamin,
|
||||||
|
alamat: data.alamat,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data kelahiran:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateKelahiran.safeParse(kelahiran.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kelahiran.edit.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/kelahiran/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: this.form.nama,
|
||||||
|
tanggal: this.form.tanggal,
|
||||||
|
jenisKelamin: this.form.jenisKelamin,
|
||||||
|
alamat: this.form.alamat,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update data kelahiran");
|
||||||
|
await kelahiran.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update data kelahiran");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating data kelahiran:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update data kelahiran"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
kelahiran.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
kelahiran.edit.id = "";
|
||||||
|
kelahiran.edit.form = { ...defaultKelahiran };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// data kematian
|
||||||
|
|
||||||
|
const templateKematian = z.object({
|
||||||
|
nama: z.string().min(1, "Nama harus diisi"),
|
||||||
|
tanggal: z.string().min(4, "Tahun harus diisi"),
|
||||||
|
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||||
|
alamat: z.string().min(1, "Alamat harus diisi"),
|
||||||
|
penyebab: z.string().min(1, "Penyebab harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultKematian = {
|
||||||
|
nama: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelamin: "",
|
||||||
|
alamat: "",
|
||||||
|
penyebab: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const kematian = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultKematian }, // ✅ ini kunci fix-nya
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateKematian.safeParse(kematian.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kematian.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.kematian["create"].post(
|
||||||
|
kematian.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
kematian.findMany.load();
|
||||||
|
return toast.success("Kematian berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan kematian");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
kematian.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
kematian.create.form = { ...defaultKematian };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.KematianGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
kematian.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kematian.findMany.page = page;
|
||||||
|
kematian.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.kematian["findMany"].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kematian.findMany.data = res.data.data ?? [];
|
||||||
|
kematian.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kematian.findMany.data = [];
|
||||||
|
kematian.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kematian paginated:", err);
|
||||||
|
kematian.findMany.data = [];
|
||||||
|
kematian.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kematian.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KematianGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/kematian/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kematian.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch kematian:", res.statusText);
|
||||||
|
kematian.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching kematian:", error);
|
||||||
|
kematian.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
kematian.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/kematian/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Kematian berhasil dihapus");
|
||||||
|
await kematian.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus kematian");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kematian");
|
||||||
|
} finally {
|
||||||
|
kematian.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultKematian },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/kematian/${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,
|
||||||
|
tanggal: data.tanggal,
|
||||||
|
jenisKelamin: data.jenisKelamin,
|
||||||
|
alamat: data.alamat,
|
||||||
|
penyebab: data.penyebab,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data kematian:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateKematian.safeParse(kematian.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kematian.edit.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/kematian/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: this.form.nama,
|
||||||
|
tanggal: this.form.tanggal,
|
||||||
|
jenisKelamin: this.form.jenisKelamin,
|
||||||
|
alamat: this.form.alamat,
|
||||||
|
penyebab: this.form.penyebab,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update data kematian");
|
||||||
|
await kematian.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update data kematian");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating data kematian:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update data kematian"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
kematian.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
kematian.edit.id = "";
|
||||||
|
kematian.edit.form = { ...defaultKematian };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const persentaseKelahiranKematian = proxy({
|
||||||
|
persentasekelahiran,
|
||||||
|
kelahiran,
|
||||||
|
kematian
|
||||||
|
});
|
||||||
|
|
||||||
|
export default persentaseKelahiranKematian;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -20,17 +21,41 @@ const defaultForm = {
|
|||||||
|
|
||||||
const infoWabahPenyakit = proxy({
|
const infoWabahPenyakit = proxy({
|
||||||
findMany: {
|
findMany: {
|
||||||
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
|
data: null as
|
||||||
include: {
|
| Prisma.InfoWabahPenyakitGetPayload<{
|
||||||
image: true;
|
include: {
|
||||||
};
|
image: true;
|
||||||
}>[],
|
};
|
||||||
async load() {
|
}>[]
|
||||||
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
|
| null,
|
||||||
"find-many"
|
page: 1,
|
||||||
].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
infoWabahPenyakit.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
infoWabahPenyakit.findMany.page = page;
|
||||||
|
infoWabahPenyakit.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.infowabahpenyakit["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
infoWabahPenyakit.findMany.data = res.data.data ?? [];
|
||||||
|
infoWabahPenyakit.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
infoWabahPenyakit.findMany.data = [];
|
||||||
|
infoWabahPenyakit.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch info wabah penyakit paginated:", err);
|
||||||
|
infoWabahPenyakit.findMany.data = [];
|
||||||
|
infoWabahPenyakit.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
infoWabahPenyakit.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -5,204 +6,241 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
name: z.string().min(3, "Judul minimal 3 karakter"),
|
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
imageId: z.string().nonempty(),
|
imageId: z.string().nonempty(),
|
||||||
})
|
|
||||||
|
|
||||||
const defaultForm = {
|
|
||||||
name: "",
|
|
||||||
deskripsi: "",
|
|
||||||
imageId: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
const kontakDarurat = proxy({
|
|
||||||
findMany: {
|
|
||||||
data: [] as Prisma.KontakDaruratGetPayload<{
|
|
||||||
include: {
|
|
||||||
image: true;
|
|
||||||
};
|
|
||||||
}>[],
|
|
||||||
async load() {
|
|
||||||
const res = await ApiFetch.api.kesehatan.kontakdarurat[
|
|
||||||
"find-many"
|
|
||||||
].get();
|
|
||||||
if (res.status === 200) {
|
|
||||||
kontakDarurat.findMany.data = res.data?.data ?? [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
create:{
|
|
||||||
form: {...defaultForm},
|
|
||||||
loading: false,
|
|
||||||
async create() {
|
|
||||||
const cek = templateForm.safeParse(kontakDarurat.create.form);
|
|
||||||
if (!cek.success) {
|
|
||||||
const err = `[${cek.error.issues
|
|
||||||
.map((v) => `${v.path.join(".")}`)
|
|
||||||
.join("\n")}] required`;
|
|
||||||
return toast.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
kontakDarurat.create.loading = true;
|
|
||||||
const res = await ApiFetch.api.kesehatan.kontakdarurat[
|
|
||||||
"create"
|
|
||||||
].post(kontakDarurat.create.form);
|
|
||||||
if (res.status === 200) {
|
|
||||||
kontakDarurat.findMany.load();
|
|
||||||
return toast.success("Kontak Darurat berhasil disimpan!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return toast.error("Gagal menyimpan kontak darurat");
|
|
||||||
} catch (error) {
|
|
||||||
console.log((error as Error).message);
|
|
||||||
} finally {
|
|
||||||
kontakDarurat.create.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetForm() {
|
|
||||||
kontakDarurat.create.form = {...defaultForm};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
findUnique: {
|
|
||||||
data: null as Prisma.KontakDaruratGetPayload<{
|
|
||||||
include: {
|
|
||||||
image: true;
|
|
||||||
};
|
|
||||||
}> | null,
|
|
||||||
async load(id: string) {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json();
|
|
||||||
kontakDarurat.findUnique.data = data.data ?? null;
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch data", res.status, res.statusText);
|
|
||||||
kontakDarurat.findUnique.data = null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching data:", error);
|
|
||||||
kontakDarurat.findUnique.data = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
loading: false,
|
|
||||||
async byId(id: string) {
|
|
||||||
try {
|
|
||||||
kontakDarurat.delete.loading = true;
|
|
||||||
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && result?.success) {
|
|
||||||
toast.success(result.message || "Kontak darurat berhasil dihapus");
|
|
||||||
await kontakDarurat.findMany.load(); // refresh list
|
|
||||||
} else {
|
|
||||||
toast.error(result?.message || "Gagal menghapus kontak darurat");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Gagal delete:", error);
|
|
||||||
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
|
|
||||||
} finally {
|
|
||||||
kontakDarurat.delete.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
id: "",
|
|
||||||
form: { ...defaultForm },
|
|
||||||
loading: false,
|
|
||||||
|
|
||||||
async load(id: string) {
|
|
||||||
if (!id) {
|
|
||||||
toast.warn("ID tidak valid");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const result = await response.json();
|
|
||||||
if (result?.success) {
|
|
||||||
const data = result.data;
|
|
||||||
this.id = data.id;
|
|
||||||
this.form = {
|
|
||||||
name: data.name,
|
|
||||||
deskripsi: data.deskripsi,
|
|
||||||
imageId: data.imageId,
|
|
||||||
};
|
|
||||||
return data; // Return the loaded data
|
|
||||||
} else {
|
|
||||||
throw new Error(result?.message || "Gagal memuat data");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching kontak darurat:", error);
|
|
||||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async update() {
|
|
||||||
const cek = templateForm.safeParse(kontakDarurat.edit.form);
|
|
||||||
if (!cek.success) {
|
|
||||||
const err = `[${cek.error.issues
|
|
||||||
.map((v) => `${v.path.join(".")}`)
|
|
||||||
.join("\n")}] required`;
|
|
||||||
return toast.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
kontakDarurat.edit.loading = true;
|
|
||||||
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.id}`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: this.form.name,
|
|
||||||
deskripsi: this.form.deskripsi,
|
|
||||||
imageId: this.form.imageId,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
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 || "Kontak darurat berhasil diupdate");
|
|
||||||
await kontakDarurat.findMany.load();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || "Gagal update kontak darurat");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Gagal update:", error);
|
|
||||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
kontakDarurat.edit.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
kontakDarurat.edit.id = "";
|
|
||||||
kontakDarurat.edit.form = { ...defaultForm };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default kontakDarurat
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const kontakDarurat = proxy({
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.KontakDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
kontakDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kontakDarurat.findMany.page = page;
|
||||||
|
kontakDarurat.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.kontakdarurat[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kontakDarurat.findMany.data = res.data.data ?? [];
|
||||||
|
kontakDarurat.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kontakDarurat.findMany.data = [];
|
||||||
|
kontakDarurat.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kontak darurat paginated:", err);
|
||||||
|
kontakDarurat.findMany.data = [];
|
||||||
|
kontakDarurat.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(kontakDarurat.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kontakDarurat.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.kontakdarurat["create"].post(
|
||||||
|
kontakDarurat.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
kontakDarurat.findMany.load();
|
||||||
|
return toast.success("Kontak Darurat berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan kontak darurat");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
kontakDarurat.create.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KontakDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kontakDarurat.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
kontakDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
kontakDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
try {
|
||||||
|
kontakDarurat.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Kontak darurat berhasil dihapus");
|
||||||
|
await kontakDarurat.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus kontak darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching kontak darurat:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(kontakDarurat.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kontakDarurat.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/kesehatan/kontakdarurat/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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 || "Kontak darurat berhasil diupdate");
|
||||||
|
await kontakDarurat.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update kontak darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat mengupdate kontak darurat"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
kontakDarurat.edit.id = "";
|
||||||
|
kontakDarurat.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kontakDarurat;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -17,21 +18,45 @@ const defaultForm = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const penangananDarurat = proxy({
|
const penangananDarurat = proxy({
|
||||||
findMany: {
|
findMany: {
|
||||||
data: [] as Prisma.PenangananDaruratGetPayload<{
|
data: null as
|
||||||
include: {
|
| Prisma.PenangananDaruratGetPayload<{
|
||||||
image: true;
|
include: {
|
||||||
};
|
image: true;
|
||||||
}>[],
|
};
|
||||||
async load() {
|
}>[]
|
||||||
const res = await ApiFetch.api.kesehatan.penanganandarurat[
|
| null,
|
||||||
"find-many"
|
page: 1,
|
||||||
].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
penangananDarurat.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
}
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
},
|
penangananDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
penangananDarurat.findMany.page = page;
|
||||||
|
penangananDarurat.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.penanganandarurat["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
penangananDarurat.findMany.data = res.data.data ?? [];
|
||||||
|
penangananDarurat.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
penangananDarurat.findMany.data = [];
|
||||||
|
penangananDarurat.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
penangananDarurat.findMany.data = [];
|
||||||
|
penangananDarurat.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
penangananDarurat.findMany.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
create:{
|
create:{
|
||||||
form: {...defaultForm},
|
form: {...defaultForm},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -9,6 +10,7 @@ const templateForm = z.object({
|
|||||||
nomor: z.string().min(1, { message: "Nomor is required" }),
|
nomor: z.string().min(1, { message: "Nomor is required" }),
|
||||||
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||||
imageId: z.string().nonempty(),
|
imageId: z.string().nonempty(),
|
||||||
|
jadwalPelayanan: z.string().min(1, { message: "Jadwal Pelayanan is required" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -16,6 +18,7 @@ const defaultForm = {
|
|||||||
nomor: "",
|
nomor: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
|
jadwalPelayanan: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const posyandustate = proxy({
|
const posyandustate = proxy({
|
||||||
@@ -50,19 +53,43 @@ const posyandustate = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.PosyanduGetPayload<{
|
| Prisma.PosyanduGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
posyandustate.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
posyandustate.findMany.page = page;
|
||||||
|
posyandustate.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
posyandustate.findMany.data = res.data.data ?? [];
|
||||||
|
posyandustate.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
posyandustate.findMany.data = [];
|
||||||
|
posyandustate.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
}>[]
|
} catch (err) {
|
||||||
| null,
|
console.error("Gagal fetch posyandu paginated:", err);
|
||||||
async load() {
|
posyandustate.findMany.data = [];
|
||||||
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get();
|
posyandustate.findMany.totalPages = 1;
|
||||||
if (res.status === 200) {
|
} finally {
|
||||||
posyandustate.findMany.data = res.data?.data ?? [];
|
posyandustate.findMany.loading = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as
|
data: null as
|
||||||
@@ -148,6 +175,7 @@ const posyandustate = proxy({
|
|||||||
nomor: data.nomor,
|
nomor: data.nomor,
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi,
|
||||||
imageId: data.imageId || "",
|
imageId: data.imageId || "",
|
||||||
|
jadwalPelayanan: data.jadwalPelayanan || "",
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -181,6 +209,7 @@ const posyandustate = proxy({
|
|||||||
nomor: this.form.nomor,
|
nomor: this.form.nomor,
|
||||||
deskripsi: this.form.deskripsi,
|
deskripsi: this.form.deskripsi,
|
||||||
imageId: this.form.imageId,
|
imageId: this.form.imageId,
|
||||||
|
jadwalPelayanan: this.form.jadwalPelayanan,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -20,17 +21,43 @@ const defaultForm = {
|
|||||||
|
|
||||||
const programKesehatan = proxy({
|
const programKesehatan = proxy({
|
||||||
findMany: {
|
findMany: {
|
||||||
data: [] as Prisma.ProgramKesehatanGetPayload<{
|
data: null as
|
||||||
include: {
|
| Prisma.ProgramKesehatanGetPayload<{
|
||||||
image: true;
|
include: {
|
||||||
};
|
image: true;
|
||||||
}>[],
|
};
|
||||||
async load() {
|
}>[]
|
||||||
const res = await ApiFetch.api.kesehatan.programkesehatan[
|
| null,
|
||||||
"find-many"
|
page: 1,
|
||||||
].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
programKesehatan.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
programKesehatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
programKesehatan.findMany.page = page;
|
||||||
|
programKesehatan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.programkesehatan[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
programKesehatan.findMany.data = res.data.data ?? [];
|
||||||
|
programKesehatan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
programKesehatan.findMany.data = [];
|
||||||
|
programKesehatan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
programKesehatan.findMany.data = [];
|
||||||
|
programKesehatan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
programKesehatan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -97,12 +124,15 @@ const programKesehatan = proxy({
|
|||||||
try {
|
try {
|
||||||
programKesehatan.delete.loading = true;
|
programKesehatan.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
|
const response = await fetch(
|
||||||
method: "DELETE",
|
`/api/kesehatan/programkesehatan/del/${id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "DELETE",
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (response.ok && result?.success) {
|
if (response.ok && result?.success) {
|
||||||
@@ -156,57 +186,70 @@ const programKesehatan = proxy({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching program kesehatan:", error);
|
console.error("Error fetching program kesehatan:", error);
|
||||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const cek = templateForm.safeParse(programKesehatan.edit.form);
|
const cek = templateForm.safeParse(programKesehatan.edit.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
.join("\n")}] required`;
|
.join("\n")}] required`;
|
||||||
return toast.error(err);
|
return toast.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
programKesehatan.edit.loading = true;
|
programKesehatan.edit.loading = true;
|
||||||
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
|
const response = await fetch(
|
||||||
method: "PUT",
|
`/api/kesehatan/programkesehatan/${this.id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "PUT",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({
|
"Content-Type": "application/json",
|
||||||
name: this.form.name,
|
},
|
||||||
deskripsiSingkat: this.form.deskripsiSingkat,
|
body: JSON.stringify({
|
||||||
deskripsi: this.form.deskripsi,
|
name: this.form.name,
|
||||||
imageId: this.form.imageId,
|
deskripsiSingkat: this.form.deskripsiSingkat,
|
||||||
}),
|
deskripsi: this.form.deskripsi,
|
||||||
});
|
imageId: this.form.imageId,
|
||||||
if (!response.ok) {
|
}),
|
||||||
const errorData = await response.json().catch(() => ({}));
|
}
|
||||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
);
|
||||||
}
|
if (!response.ok) {
|
||||||
const result = await response.json();
|
const errorData = await response.json().catch(() => ({}));
|
||||||
if (result.success) {
|
throw new Error(
|
||||||
toast.success(result.message || "Program kesehatan berhasil diupdate");
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
await programKesehatan.findMany.load();
|
);
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || "Gagal update program kesehatan");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Gagal update:", error);
|
|
||||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
programKesehatan.edit.loading = false;
|
|
||||||
}
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "Program kesehatan berhasil diupdate"
|
||||||
|
);
|
||||||
|
await programKesehatan.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update program kesehatan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat mengupdate program kesehatan"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
programKesehatan.edit.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
programKesehatan.edit.id = "";
|
programKesehatan.edit.id = "";
|
||||||
programKesehatan.edit.form = { ...defaultForm };
|
programKesehatan.edit.form = { ...defaultForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -163,13 +164,43 @@ const puskesmasState = proxy({
|
|||||||
},
|
},
|
||||||
|
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Prisma.PuskesmasGetPayload<{
|
data: null as
|
||||||
include: { image: true; jam: true; kontak: true };
|
| Prisma.PuskesmasGetPayload<{
|
||||||
}>[] | null,
|
include: {
|
||||||
async load() {
|
image: true;
|
||||||
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
|
jam: true;
|
||||||
if (res.status === 200) {
|
kontak: true;
|
||||||
puskesmasState.findMany.data = res.data?.data ?? [];
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
puskesmasState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
puskesmasState.findMany.page = page;
|
||||||
|
puskesmasState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
puskesmasState.findMany.data = res.data.data ?? [];
|
||||||
|
puskesmasState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
puskesmasState.findMany.data = [];
|
||||||
|
puskesmasState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch berita paginated:", err);
|
||||||
|
puskesmasState.findMany.data = [];
|
||||||
|
puskesmasState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
puskesmasState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
834
src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts
Normal file
834
src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts
Normal file
@@ -0,0 +1,834 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Template form responden
|
||||||
|
|
||||||
|
const templateResponden = z.object({
|
||||||
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
|
tanggal: z.string().min(1, "Tanggal harus diisi"),
|
||||||
|
jenisKelaminId: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||||
|
ratingId: z.string().min(1, "Rating harus diisi"),
|
||||||
|
kelompokUmurId: z.string().min(1, "Kelompok umur harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormResponden = {
|
||||||
|
name: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelaminId: "",
|
||||||
|
ratingId: "",
|
||||||
|
kelompokUmurId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const responden = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormResponden },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateResponden.safeParse(responden.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||||
|
toast.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
responden.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.landingpage.responden["create"].post(
|
||||||
|
responden.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Responden berhasil ditambahkan");
|
||||||
|
await responden.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(res.data?.message ?? "Gagal tambah responden");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal create:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menambahkan responden");
|
||||||
|
} finally {
|
||||||
|
responden.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as any[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
load: async (page = 1, limit = 10) => {
|
||||||
|
// Change to arrow function
|
||||||
|
responden.findMany.loading = true; // Use the full path to access the property
|
||||||
|
responden.findMany.page = page;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.landingpage.responden["findMany"].get({
|
||||||
|
query: { page, limit },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
responden.findMany.data = res.data.data || [];
|
||||||
|
responden.findMany.total = res.data.total || 0;
|
||||||
|
responden.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load responden:", res.data?.message);
|
||||||
|
responden.findMany.data = [];
|
||||||
|
responden.findMany.total = 0;
|
||||||
|
responden.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading responden:", error);
|
||||||
|
responden.findMany.data = [];
|
||||||
|
responden.findMany.total = 0;
|
||||||
|
responden.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
responden.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.RespondenGetPayload<{
|
||||||
|
include: {
|
||||||
|
jenisKelamin: true;
|
||||||
|
rating: true;
|
||||||
|
kelompokUmur: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/landingpage/responden/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
responden.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
responden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading responden:", error);
|
||||||
|
responden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormResponden },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
responden.update.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/landingpage/responden/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
tanggal: data.tanggal,
|
||||||
|
jenisKelaminId: data.jenisKelaminId,
|
||||||
|
ratingId: data.ratingId,
|
||||||
|
kelompokUmurId: data.kelompokUmurId,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading responden:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
responden.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templateResponden.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/landingpage/responden/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
tanggal: this.form.tanggal,
|
||||||
|
jenisKelaminId: this.form.jenisKelaminId,
|
||||||
|
ratingId: this.form.ratingId,
|
||||||
|
kelompokUmurId: this.form.kelompokUmurId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await responden.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data responden");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
responden.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/landingpage/responden/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "responden berhasil dihapus");
|
||||||
|
await responden.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus responden");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus responden");
|
||||||
|
} finally {
|
||||||
|
responden.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Template form jenis kelamin responden
|
||||||
|
const templateJenisKelaminResponden = z.object({
|
||||||
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormJenisKelaminResponden = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const jenisKelaminResponden = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormJenisKelaminResponden },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateJenisKelaminResponden.safeParse(
|
||||||
|
jenisKelaminResponden.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||||
|
toast.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jenisKelaminResponden.create.loading = true;
|
||||||
|
try {
|
||||||
|
jenisKelaminResponden.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.landingpage.jeniskelaminresponden[
|
||||||
|
"create"
|
||||||
|
].post(jenisKelaminResponden.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Jenis kelamin responden berhasil ditambahkan");
|
||||||
|
await jenisKelaminResponden.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
res.data?.message ?? "Gagal tambah jenis kelamin responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal create:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menambahkan jenis kelamin responden"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
jenisKelaminResponden.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as any[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
load: async (page = 1, limit = 10) => {
|
||||||
|
// Change to arrow function
|
||||||
|
jenisKelaminResponden.findMany.loading = true; // Use the full path to access the property
|
||||||
|
jenisKelaminResponden.findMany.page = page;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.landingpage.jeniskelaminresponden[
|
||||||
|
"findMany"
|
||||||
|
].get({
|
||||||
|
query: { page, limit },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jenisKelaminResponden.findMany.data = res.data.data || [];
|
||||||
|
jenisKelaminResponden.findMany.total = res.data.total || 0;
|
||||||
|
jenisKelaminResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load jenis kelamin responden:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
jenisKelaminResponden.findMany.data = [];
|
||||||
|
jenisKelaminResponden.findMany.total = 0;
|
||||||
|
jenisKelaminResponden.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading jenis kelamin responden:", error);
|
||||||
|
jenisKelaminResponden.findMany.data = [];
|
||||||
|
jenisKelaminResponden.findMany.total = 0;
|
||||||
|
jenisKelaminResponden.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
jenisKelaminResponden.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.JenisKelaminRespondenGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/landingpage/jeniskelaminresponden/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
jenisKelaminResponden.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
jenisKelaminResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading jenis kelamin responden:", error);
|
||||||
|
jenisKelaminResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormJenisKelaminResponden },
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templateJenisKelaminResponden.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/landingpage/jeniskelaminresponden/${id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await jenisKelaminResponden.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data jenis kelamin responden");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
jenisKelaminResponden.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/landingpage/jeniskelaminresponden/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "jenis kelamin responden berhasil dihapus"
|
||||||
|
);
|
||||||
|
await jenisKelaminResponden.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus jenis kelamin responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus jenis kelamin responden");
|
||||||
|
} finally {
|
||||||
|
jenisKelaminResponden.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Template form pilihan rating responden
|
||||||
|
|
||||||
|
const templatePilihanRatingResponden = z.object({
|
||||||
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormPilihanRatingResponden = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const pilihanRatingResponden = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormPilihanRatingResponden },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templatePilihanRatingResponden.safeParse(
|
||||||
|
pilihanRatingResponden.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||||
|
toast.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pilihanRatingResponden.create.loading = true;
|
||||||
|
try {
|
||||||
|
pilihanRatingResponden.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.landingpage.pilihanratingresponden[
|
||||||
|
"create"
|
||||||
|
].post(pilihanRatingResponden.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Jenis kelamin responden berhasil ditambahkan");
|
||||||
|
await pilihanRatingResponden.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
res.data?.message ?? "Gagal tambah jenis kelamin responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal create:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menambahkan jenis kelamin responden"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
pilihanRatingResponden.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as any[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
load: async (page = 1, limit = 10) => {
|
||||||
|
// Change to arrow function
|
||||||
|
pilihanRatingResponden.findMany.loading = true; // Use the full path to access the property
|
||||||
|
pilihanRatingResponden.findMany.page = page;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.landingpage.pilihanratingresponden[
|
||||||
|
"findMany"
|
||||||
|
].get({
|
||||||
|
query: { page, limit },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pilihanRatingResponden.findMany.data = res.data.data || [];
|
||||||
|
pilihanRatingResponden.findMany.total = res.data.total || 0;
|
||||||
|
pilihanRatingResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load pilihan rating responden:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
pilihanRatingResponden.findMany.data = [];
|
||||||
|
pilihanRatingResponden.findMany.total = 0;
|
||||||
|
pilihanRatingResponden.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pilihan rating responden:", error);
|
||||||
|
pilihanRatingResponden.findMany.data = [];
|
||||||
|
pilihanRatingResponden.findMany.total = 0;
|
||||||
|
pilihanRatingResponden.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pilihanRatingResponden.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PilihanRatingRespondenGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/landingpage/pilihanratingresponden/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
pilihanRatingResponden.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
pilihanRatingResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pilihan rating responden:", error);
|
||||||
|
pilihanRatingResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormPilihanRatingResponden },
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templatePilihanRatingResponden.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/landingpage/pilihanratingresponden/${id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await pilihanRatingResponden.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data pilihan rating responden");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
pilihanRatingResponden.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/landingpage/pilihanratingresponden/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "pilihan rating responden berhasil dihapus"
|
||||||
|
);
|
||||||
|
await pilihanRatingResponden.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus pilihan rating responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus pilihan rating responden");
|
||||||
|
} finally {
|
||||||
|
pilihanRatingResponden.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Template form kelompok umur responden
|
||||||
|
|
||||||
|
const templateKelompokUmurResponden = z.object({
|
||||||
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormKelompokUmurResponden = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const kelompokUmurResponden = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormKelompokUmurResponden },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateKelompokUmurResponden.safeParse(
|
||||||
|
kelompokUmurResponden.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||||
|
toast.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kelompokUmurResponden.create.loading = true;
|
||||||
|
try {
|
||||||
|
kelompokUmurResponden.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.landingpage.umurresponden["create"].post(
|
||||||
|
kelompokUmurResponden.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Kelompok umur responden berhasil ditambahkan");
|
||||||
|
await kelompokUmurResponden.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
res.data?.message ?? "Gagal tambah kelompok umur responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal create:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menambahkan kelompok umur responden"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
kelompokUmurResponden.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as any[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
load: async (page = 1, limit = 10) => {
|
||||||
|
// Change to arrow function
|
||||||
|
kelompokUmurResponden.findMany.loading = true; // Use the full path to access the property
|
||||||
|
kelompokUmurResponden.findMany.page = page;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.landingpage.umurresponden[
|
||||||
|
"findMany"
|
||||||
|
].get({
|
||||||
|
query: { page, limit },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kelompokUmurResponden.findMany.data = res.data.data || [];
|
||||||
|
kelompokUmurResponden.findMany.total = res.data.total || 0;
|
||||||
|
kelompokUmurResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load kelompok umur responden:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
kelompokUmurResponden.findMany.data = [];
|
||||||
|
kelompokUmurResponden.findMany.total = 0;
|
||||||
|
kelompokUmurResponden.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kelompok umur responden:", error);
|
||||||
|
kelompokUmurResponden.findMany.data = [];
|
||||||
|
kelompokUmurResponden.findMany.total = 0;
|
||||||
|
kelompokUmurResponden.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kelompokUmurResponden.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.UmurRespondenGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/landingpage/umurresponden/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kelompokUmurResponden.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
kelompokUmurResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kelompok umur responden:", error);
|
||||||
|
kelompokUmurResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormKelompokUmurResponden },
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templateKelompokUmurResponden.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/landingpage/umurresponden/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await kelompokUmurResponden.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data kelompok umur responden");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
kelompokUmurResponden.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/landingpage/umurresponden/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "kelompok umur responden berhasil dihapus"
|
||||||
|
);
|
||||||
|
await kelompokUmurResponden.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus kelompok umur responden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kelompok umur responden");
|
||||||
|
} finally {
|
||||||
|
kelompokUmurResponden.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const indeksKepuasanState = proxy({
|
||||||
|
responden,
|
||||||
|
kelompokUmurResponden,
|
||||||
|
jenisKelaminResponden,
|
||||||
|
pilihanRatingResponden
|
||||||
|
})
|
||||||
|
|
||||||
|
export default indeksKepuasanState
|
||||||
@@ -49,35 +49,38 @@ const daftarInformasiPublik = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as any[] | null,
|
data: null as
|
||||||
|
| Prisma.DaftarInformasiPublikGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
daftarInformasiPublik.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
daftarInformasiPublik.findMany.page = page;
|
daftarInformasiPublik.findMany.page = page;
|
||||||
try {
|
daftarInformasiPublik.findMany.search = search;
|
||||||
const res = await ApiFetch.api.ppid.daftarinformasipublik[
|
|
||||||
"find-many"
|
|
||||||
].get({
|
|
||||||
query: { page, limit },
|
|
||||||
});
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get({ query });
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
daftarInformasiPublik.findMany.data = res.data.data || [];
|
daftarInformasiPublik.findMany.data = res.data.data ?? [];
|
||||||
daftarInformasiPublik.findMany.total = res.data.total || 0;
|
daftarInformasiPublik.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
|
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to load daftar informasi publik:", res.data?.message);
|
|
||||||
daftarInformasiPublik.findMany.data = [];
|
daftarInformasiPublik.findMany.data = [];
|
||||||
daftarInformasiPublik.findMany.total = 0;
|
|
||||||
daftarInformasiPublik.findMany.totalPages = 1;
|
daftarInformasiPublik.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Error loading daftar informasi publik:", error);
|
console.error("Gagal fetch daftar informasi publik paginated:", err);
|
||||||
daftarInformasiPublik.findMany.data = [];
|
daftarInformasiPublik.findMany.data = [];
|
||||||
daftarInformasiPublik.findMany.total = 0;
|
|
||||||
daftarInformasiPublik.findMany.totalPages = 1;
|
daftarInformasiPublik.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
daftarInformasiPublik.findMany.loading = false;
|
daftarInformasiPublik.findMany.loading = false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|||||||
const templateGrafikJenisKelamin = z.object({
|
const templateGrafikJenisKelamin = z.object({
|
||||||
laki: z.string().min(1, "Data laki-laki harus diisi"),
|
laki: z.string().min(1, "Data laki-laki harus diisi"),
|
||||||
perempuan: z.string().min(1, "Data perempuan harus diisi"),
|
perempuan: z.string().min(1, "Data perempuan harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
laki: "",
|
laki: "",
|
||||||
@@ -17,10 +17,12 @@ const defaultForm = {
|
|||||||
|
|
||||||
const grafikBerdasarkanJenisKelamin = proxy({
|
const grafikBerdasarkanJenisKelamin = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {...defaultForm},
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async create(){
|
async create() {
|
||||||
const cek = templateGrafikJenisKelamin.safeParse(grafikBerdasarkanJenisKelamin.create.form);
|
const cek = templateGrafikJenisKelamin.safeParse(
|
||||||
|
grafikBerdasarkanJenisKelamin.create.form
|
||||||
|
);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||||
toast.error(err);
|
toast.error(err);
|
||||||
@@ -33,14 +35,20 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
"create"
|
"create"
|
||||||
].post(grafikBerdasarkanJenisKelamin.create.form);
|
].post(grafikBerdasarkanJenisKelamin.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success("Grafik berdasarkan jenis kelamin berhasil ditambahkan");
|
toast.success(
|
||||||
|
"Grafik berdasarkan jenis kelamin berhasil ditambahkan"
|
||||||
|
);
|
||||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
await grafikBerdasarkanJenisKelamin.findMany.load();
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin");
|
toast.error(
|
||||||
|
res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal create:", error);
|
console.error("Gagal create:", error);
|
||||||
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin");
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin"
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
grafikBerdasarkanJenisKelamin.create.loading = false;
|
grafikBerdasarkanJenisKelamin.create.loading = false;
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
load: async (page = 1, limit = 10) => {
|
||||||
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
|
// Change to arrow function
|
||||||
|
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
|
||||||
grafikBerdasarkanJenisKelamin.findMany.page = page;
|
grafikBerdasarkanJenisKelamin.findMany.page = page;
|
||||||
try {
|
try {
|
||||||
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
|
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
|
||||||
@@ -61,13 +70,17 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query: { page, limit },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
grafikBerdasarkanJenisKelamin.findMany.data = res.data.data || [];
|
grafikBerdasarkanJenisKelamin.findMany.data = res.data.data || [];
|
||||||
grafikBerdasarkanJenisKelamin.findMany.total = res.data.total || 0;
|
grafikBerdasarkanJenisKelamin.findMany.total = res.data.total || 0;
|
||||||
grafikBerdasarkanJenisKelamin.findMany.totalPages = res.data.totalPages || 1;
|
grafikBerdasarkanJenisKelamin.findMany.totalPages =
|
||||||
|
res.data.totalPages || 1;
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to load grafik berdasarkan jenis kelamin:", res.data?.message);
|
console.error(
|
||||||
|
"Failed to load grafik berdasarkan jenis kelamin:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
grafikBerdasarkanJenisKelamin.findMany.data = [];
|
grafikBerdasarkanJenisKelamin.findMany.data = [];
|
||||||
grafikBerdasarkanJenisKelamin.findMany.total = 0;
|
grafikBerdasarkanJenisKelamin.findMany.total = 0;
|
||||||
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
|
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
|
||||||
@@ -106,7 +119,7 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
id: "",
|
id: "",
|
||||||
form: {...defaultForm},
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId() {
|
async byId() {
|
||||||
// Method implementation if needed
|
// Method implementation if needed
|
||||||
@@ -119,20 +132,24 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
}
|
}
|
||||||
const cek = templateGrafikJenisKelamin.safeParse(this.form);
|
const cek = templateGrafikJenisKelamin.safeParse(this.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
toast.error(err);
|
toast.error(err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`, {
|
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`,
|
||||||
method: "PUT",
|
{
|
||||||
headers: {
|
method: "PUT",
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
},
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify(this.form),
|
},
|
||||||
});
|
body: JSON.stringify(this.form),
|
||||||
|
}
|
||||||
|
);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (!response.ok || !result?.success) {
|
if (!response.ok || !result?.success) {
|
||||||
throw new Error(result?.message || "Gagal update data");
|
throw new Error(result?.message || "Gagal update data");
|
||||||
@@ -156,29 +173,40 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
try {
|
try {
|
||||||
grafikBerdasarkanJenisKelamin.delete.loading = true;
|
grafikBerdasarkanJenisKelamin.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`, {
|
const response = await fetch(
|
||||||
method: "DELETE",
|
`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "DELETE",
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (response.ok && result?.success) {
|
if (response.ok && result?.success) {
|
||||||
toast.success(result.message || "Grafik berdasarkan jenis kelamin berhasil dihapus");
|
toast.success(
|
||||||
|
result.message ||
|
||||||
|
"Grafik berdasarkan jenis kelamin berhasil dihapus"
|
||||||
|
);
|
||||||
await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list
|
await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list
|
||||||
} else {
|
} else {
|
||||||
toast.error(result?.message || "Gagal menghapus grafik berdasarkan jenis kelamin");
|
toast.error(
|
||||||
|
result?.message ||
|
||||||
|
"Gagal menghapus grafik berdasarkan jenis kelamin"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal delete:", error);
|
console.error("Gagal delete:", error);
|
||||||
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin");
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin"
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
grafikBerdasarkanJenisKelamin.delete.loading = false;
|
grafikBerdasarkanJenisKelamin.delete.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default grafikBerdasarkanJenisKelamin;
|
export default grafikBerdasarkanJenisKelamin;
|
||||||
|
|||||||
63
src/app/admin/(dashboard)/_state/state-file-storage.ts
Normal file
63
src/app/admin/(dashboard)/_state/state-file-storage.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
|
||||||
|
interface FileItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
link: string;
|
||||||
|
mimeType: string;
|
||||||
|
category: string;
|
||||||
|
realName: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string | Date;
|
||||||
|
updatedAt: string | Date;
|
||||||
|
deletedAt: string | Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateFileStorage = proxy<{
|
||||||
|
list: FileItem[] | null;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
total: number | undefined;
|
||||||
|
load: (params?: { search?: string }) => Promise<void>;
|
||||||
|
del: (params: { id: string }) => Promise<void>;
|
||||||
|
}>({
|
||||||
|
list: null,
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
total: undefined,
|
||||||
|
async load(params?: { search?: string }) {
|
||||||
|
const { search = "" } = params ?? {};
|
||||||
|
try {
|
||||||
|
const { data } = await ApiFetch.api.fileStorage.findMany.get({
|
||||||
|
query: {
|
||||||
|
page: this.page,
|
||||||
|
limit: this.limit,
|
||||||
|
search,
|
||||||
|
category: 'image'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.data) {
|
||||||
|
this.list = data.data as FileItem[];
|
||||||
|
this.total = data.meta?.totalPages;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading files:', error);
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async del({ id }: { id: string }) {
|
||||||
|
try {
|
||||||
|
await ApiFetch.api.fileStorage.delete({ id });
|
||||||
|
await this.load();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting file:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stateFileStorage;
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import stateDashboardBerita from "@/app/admin/(dashboard)/_state/desa/berita";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
Paper,
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
import { Dropzone } from "@mantine/dropzone";
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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";
|
||||||
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
|
||||||
import colors from "@/con/colors";
|
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
|
||||||
import { FileInput } from "@mantine/core";
|
|
||||||
import stateDashboardBerita from "@/app/admin/(dashboard)/_state/desa/berita";
|
|
||||||
|
|
||||||
|
|
||||||
function EditBerita() {
|
function EditBerita() {
|
||||||
@@ -130,27 +130,62 @@ function EditBerita() {
|
|||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ function DetailBerita() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (beritaState.berita.findUnique.data) {
|
if (beritaState.berita.findUnique.data) {
|
||||||
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
|
router.push(`/admin/desa/berita/list-berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!beritaState.berita.findUnique.data}
|
disabled={!beritaState.berita.findUnique.data}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
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, Center, FileInput, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -113,25 +114,62 @@ export default function CreateBerita() {
|
|||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
|
|||||||
@@ -39,23 +39,17 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
} = beritaState.berita.findMany;
|
} = beritaState.berita.findMany;
|
||||||
|
|
||||||
|
|
||||||
// Fetch pertama kali
|
// Fetch data when page or search changes
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10); // awal page = 1
|
load(page, 10, search);
|
||||||
}, [page]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (data || []).filter((item) => {
|
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.kategoriBerita?.name.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return <Skeleton h={500} />;
|
return <Skeleton h={500} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors["white-1"]} p={"md"}>
|
<Paper bg={colors["white-1"]} p={"md"}>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
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 ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -18,6 +19,11 @@ function EditFoto() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
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);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: fotoState.update.form.name || '',
|
||||||
|
deskripsi: fotoState.update.form.deskripsi || '',
|
||||||
|
imagesId: fotoState.update.form.imagesId || ''
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadFoto = async () => {
|
const loadFoto = async () => {
|
||||||
@@ -26,6 +32,11 @@ function EditFoto() {
|
|||||||
try {
|
try {
|
||||||
const data = await fotoState.update.load(id);
|
const data = await fotoState.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imagesId: data.imageGalleryFoto?.id || ''
|
||||||
|
});
|
||||||
if (data?.imageGalleryFoto?.link) {
|
if (data?.imageGalleryFoto?.link) {
|
||||||
setPreviewImage(data.imageGalleryFoto.link);
|
setPreviewImage(data.imageGalleryFoto.link);
|
||||||
}
|
}
|
||||||
@@ -40,6 +51,12 @@ function EditFoto() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
fotoState.update.form = {
|
||||||
|
...fotoState.update.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imagesId: formData.imagesId
|
||||||
|
};
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
@@ -74,30 +91,55 @@ function EditFoto() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||||
placeholder='Masukkan judul foto'
|
placeholder='Masukkan judul foto'
|
||||||
value={fotoState.update.form.name}
|
value={formData.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
(fotoState.update.form.name = e.target.value)
|
(formData.name = e.target.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
<Text>Upload Foto</Text>
|
||||||
value={file}
|
<Dropzone
|
||||||
onChange={async (e) => {
|
onDrop={(files) => {
|
||||||
if (!e) return;
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
setFile(e);
|
if (selectedFile) {
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
setFile(selectedFile);
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
);
|
}
|
||||||
setPreviewImage(base64);
|
}}
|
||||||
}}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
/>
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
{previewImage ? (
|
accept={{ 'image/*': [] }}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
>
|
||||||
) : (
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Dropzone.Accept>
|
||||||
<IconImageInPicture />
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
</Center>
|
</Dropzone.Accept>
|
||||||
)}
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|||||||
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 ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -69,25 +70,62 @@ function CreateFoto() {
|
|||||||
fotoState.create.form.name = val.target.value;
|
fotoState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
|
|||||||
@@ -1,93 +1,124 @@
|
|||||||
'use client'
|
"use client";
|
||||||
import colors from '@/con/colors';
|
import colors from "@/con/colors";
|
||||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import stateFileStorage from "@/state/state-list-image";
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import {
|
||||||
import { useRouter } from 'next/navigation';
|
ActionIcon,
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
Box,
|
||||||
import { useProxy } from 'valtio/utils';
|
Flex,
|
||||||
import stateGallery from '../../../_state/desa/gallery';
|
Group,
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
Image,
|
||||||
import HeaderSearch from '../../../_com/header';
|
Pagination,
|
||||||
import { useState } from 'react';
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import { IconSearch, IconTrash, IconX } from "@tabler/icons-react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import toast from "react-simple-toasts";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
|
||||||
function Foto() {
|
export default function ListImage() {
|
||||||
const [search, setSearch] = useState("");
|
const { list, total } = useSnapshot(stateFileStorage);
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<HeaderSearch
|
|
||||||
title='Posisi Organisasi'
|
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<ListFoto search={search} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListFoto({ search }: { search: string }) {
|
|
||||||
const fotoState = useProxy(stateGallery.foto)
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
fotoState.findMany.load()
|
stateFileStorage.load();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const filteredData = (fotoState.findMany.data || []).filter(item => {
|
let timeOut: NodeJS.Timer;
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fotoState.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack p={"lg"}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Flex justify="space-between">
|
||||||
<JudulListTab
|
<Title order={3}>List Foto</Title>
|
||||||
title='List Foto'
|
<TextInput
|
||||||
href='/admin/desa/gallery/foto/create'
|
radius={"lg"}
|
||||||
placeholder='pencarian'
|
leftSection={<IconSearch />}
|
||||||
searchIcon={<IconSearch size={16} />}
|
rightSection={
|
||||||
|
<ActionIcon
|
||||||
|
variant="transparent"
|
||||||
|
onClick={() => {
|
||||||
|
stateFileStorage.load();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconX />
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
|
placeholder="Pencarian"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (timeOut) clearTimeout(timeOut);
|
||||||
|
timeOut = setTimeout(() => {
|
||||||
|
stateFileStorage.load({ search: e.target.value });
|
||||||
|
}, 200);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
</Flex>
|
||||||
<TableThead>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<TableTr>
|
<SimpleGrid
|
||||||
<TableTh>Judul Foto</TableTh>
|
cols={{
|
||||||
<TableTh>Tanggal Foto</TableTh>
|
base: 3,
|
||||||
<TableTh>Deskripsi Foto</TableTh>
|
md: 5,
|
||||||
<TableTh>Detail</TableTh>
|
lg: 10,
|
||||||
</TableTr>
|
}}
|
||||||
</TableThead>
|
>
|
||||||
<TableTbody>
|
{list &&
|
||||||
{filteredData.map((item) => (
|
list.map((v, k) => {
|
||||||
<TableTr key={item.id}>
|
return (
|
||||||
<TableTd>{item.name}</TableTd>
|
<Paper key={k} shadow="sm">
|
||||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
<Stack pos={"relative"} gap={0} justify="space-between">
|
||||||
<TableTd>
|
<motion.div
|
||||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
onClick={() => {
|
||||||
</TableTd>
|
// copy to clipboard
|
||||||
<TableTd>
|
navigator.clipboard.writeText(v.url);
|
||||||
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
|
toast("Berhasil disalin");
|
||||||
<IconDeviceImac size={20} />
|
}}
|
||||||
</Button>
|
whileHover={{ scale: 1.05 }}
|
||||||
</TableTd>
|
whileTap={{ scale: 0.8 }}
|
||||||
</TableTr>
|
>
|
||||||
))}
|
<Image
|
||||||
</TableTbody>
|
h={100}
|
||||||
</Table>
|
src={v.url + "?size=100"}
|
||||||
|
alt={v.name}
|
||||||
|
fit="cover"
|
||||||
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
objectFit: "cover",
|
||||||
|
objectPosition: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
<Box p={"md"} h={54}>
|
||||||
|
<Text lineClamp={2} fz={"xs"}>
|
||||||
|
{v.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="end">
|
||||||
|
<IconTrash
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
stateFileStorage.del({ name: v.name }).finally(() => {
|
||||||
|
toast("Berhasil dihapus");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
{total && (
|
||||||
|
<Pagination
|
||||||
|
total={total}
|
||||||
|
onChange={(e) => {
|
||||||
|
stateFileStorage.page = e;
|
||||||
|
stateFileStorage.load();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Foto;
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import LayoutTabsGallery from "../../ppid/_com/layoutTabsGallery"
|
import LayoutTabsGallery from "./lib/layoutTabs"
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,30 +5,20 @@ import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } 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';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Grafik Hasil Kepuasan Masyarakat",
|
label: "Foto",
|
||||||
value: "grafikhasilkepuasamanmasyarakat",
|
value: "foto",
|
||||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat"
|
href: "/admin/desa/gallery/foto"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Grafik Berdasarkan Jenis Kelamin Responden",
|
label: "Video",
|
||||||
value: "grafikberdasarkanjeniskelaminresponden",
|
value: "video",
|
||||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden"
|
href: "/admin/desa/gallery/video"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Grafik Berdasarkan Pilihan Responden",
|
|
||||||
value: "grafikberdasarkanpilihanresponden",
|
|
||||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Grafik Berdasarkan Umur Responden",
|
|
||||||
value: "grafikberdasarkanumurresponden",
|
|
||||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur"
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||||
@@ -50,7 +40,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={3}>Indeks Kepuasan Masyarakat (IKM) Desa Darmasaba</Title>
|
<Title order={3}>Gallery</Title>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
{tabs.map((e, i) => (
|
{tabs.map((e, i) => (
|
||||||
@@ -69,4 +59,4 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabsGallery;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import stateGallery from '../../../_state/desa/gallery';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
|
import stateGallery from '../../../_state/desa/gallery';
|
||||||
|
|
||||||
function Video() {
|
function Video() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -29,35 +29,34 @@ function Video() {
|
|||||||
function ListVideo({ search }: { search: string }) {
|
function ListVideo({ search }: { search: string }) {
|
||||||
const videoState = useProxy(stateGallery.video)
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = videoState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
videoState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (videoState.findMany.data || []).filter(item => {
|
const filteredData = (data || [])
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!videoState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
title='List Video'
|
title='List Video'
|
||||||
href='/admin/desa/gallery/video/create'
|
href='/admin/desa/gallery/video/create'
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
@@ -71,10 +70,25 @@ function ListVideo({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
|
||||||
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
||||||
@@ -86,6 +100,15 @@ function ListVideo({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
||||||
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, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { 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';
|
||||||
|
|
||||||
@@ -105,26 +106,62 @@ function EditPenghargaan() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
||||||
placeholder="masukkan juara"
|
placeholder="masukkan juara"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import penghargaanState from '../../../_state/desa/penghargaan';
|
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import penghargaanState from '../../../_state/desa/penghargaan';
|
||||||
|
|
||||||
|
|
||||||
function CreatePenghargaan() {
|
function CreatePenghargaan() {
|
||||||
@@ -85,25 +86,62 @@ function CreatePenghargaan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
|
||||||
|
|
||||||
function EditPengumuman() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Edit Pengumuman</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
|
||||||
placeholder='Masukkan judul'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
|
||||||
placeholder='Masukkan deskripsi singkat'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
|
||||||
placeholder='Masukkan tanggal'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Waktu</Text>}
|
|
||||||
placeholder='Masukkan waktu'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<KeamananEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPengumuman;
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "List Pengumuman",
|
||||||
|
value: "listpengumuman",
|
||||||
|
href: "/admin/desa/pengumuman/list-pengumuman"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Kategori Pengumuman",
|
||||||
|
value: "kategoripengumuman",
|
||||||
|
href: "/admin/desa/pengumuman/kategori-pengumuman"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find(t => t.value === value)
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href)
|
||||||
|
}
|
||||||
|
setActiveTab(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
|
if (match) {
|
||||||
|
setActiveTab(match.value)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Pengumuman</Title>
|
||||||
|
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||||
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
|
{tabs.map((e, i) => (
|
||||||
|
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
{tabs.map((e, i) => (
|
||||||
|
<TabsPanel key={i} value={e.value}>
|
||||||
|
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||||
|
<></>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutTabsLayanan;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditKategoriPengumuman() {
|
||||||
|
const editState = useProxy(stateDesaPengumuman.category)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: editState.update.form.name || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadKategori = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kategori Pengumuman:", error);
|
||||||
|
toast.error("Gagal memuat data kategori Pengumuman");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadKategori();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
editState.update.form = {
|
||||||
|
...editState.update.form,
|
||||||
|
name: formData.name,
|
||||||
|
};
|
||||||
|
await editState.update.update();
|
||||||
|
toast.success('Kategori Pengumuman berhasil diperbarui!');
|
||||||
|
router.push('/admin/desa/pengumuman/kategori-pengumuman');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating kategori Pengumuman:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui kategori Pengumuman');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Kategori Pengumuman</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Pengumuman</Text>}
|
||||||
|
placeholder="masukkan nama kategori Pengumuman"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditKategoriPengumuman;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client'
|
||||||
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateKategoriPengumuman() {
|
||||||
|
const createState = useProxy(stateDesaPengumuman.category)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
createState.create.form = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await createState.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/desa/pengumuman/kategori-pengumuman")
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Create Kategori Pengumuman</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Pengumuman</Text>}
|
||||||
|
placeholder='Masukkan nama kategori Pengumuman'
|
||||||
|
value={createState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateKategoriPengumuman;
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function KategoriPengumuman() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Kategori Pengumuman'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListKategoriPengumuman search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListKategoriPengumuman({ search }: { search: string }) {
|
||||||
|
const listDataState = useProxy(stateDesaPengumuman.category)
|
||||||
|
const router = useRouter();
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listDataState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
listDataState.delete.delete(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
|
||||||
|
listDataState.findMany.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||||
|
const keyword = search.toLowerCase();
|
||||||
|
return (
|
||||||
|
item.name.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!listDataState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Kategori Pengumuman'
|
||||||
|
href='/admin/desa/pengumuman/kategori-pengumuman/create'
|
||||||
|
/>
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>No</TableTh>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Hapus</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item, index) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/desa/pengumuman/kategori-pengumuman/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
disabled={listDataState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text='Apakah anda yakin ingin menghapus kategori Pengumuman ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KategoriPengumuman;
|
||||||
12
src/app/admin/(dashboard)/desa/pengumuman/layout.tsx
Normal file
12
src/app/admin/(dashboard)/desa/pengumuman/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import LayoutTabs from './_com/layoutTabs';
|
||||||
|
|
||||||
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LayoutTabs>
|
||||||
|
{children}
|
||||||
|
</LayoutTabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import stateDesaPengumuman from "@/app/admin/(dashboard)/_state/desa/pengumuman";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconArrowBack } from "@tabler/icons-react";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
|
|
||||||
|
function EditPengumuman() {
|
||||||
|
const editState = useProxy(stateDesaPengumuman);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
judul: editState.pengumuman.edit.form.judul || '',
|
||||||
|
deskripsi: editState.pengumuman.edit.form.deskripsi || '',
|
||||||
|
categoryPengumumanId: editState.pengumuman.edit.form.categoryPengumumanId || '',
|
||||||
|
content: editState.pengumuman.edit.form.content || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load pengumuman by id saat pertama kali
|
||||||
|
useEffect(() => {
|
||||||
|
editState.category.findMany.load()
|
||||||
|
const loadpengumuman = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await stateDesaPengumuman.pengumuman.edit.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
judul: data.judul || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
categoryPengumumanId: data.categoryPengumumanId || '',
|
||||||
|
content: data.content || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pengumuman:", error);
|
||||||
|
toast.error("Gagal memuat data pengumuman");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadpengumuman();
|
||||||
|
}, [params?.id]); // ✅ hapus editState dari dependency
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// edit global state with form data
|
||||||
|
editState.pengumuman.edit.form = {
|
||||||
|
...editState.pengumuman.edit.form,
|
||||||
|
judul: formData.judul,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
content: formData.content,
|
||||||
|
categoryPengumumanId: formData.categoryPengumumanId || ''
|
||||||
|
};
|
||||||
|
await editState.pengumuman.edit.update();
|
||||||
|
toast.success("pengumuman berhasil diperbarui!");
|
||||||
|
router.push("/admin/desa/pengumuman/list-pengumuman");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating pengumuman:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui pengumuman");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit pengumuman</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.judul}
|
||||||
|
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.content}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||||
|
editState.pengumuman.edit.form.content = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={formData.categoryPengumumanId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, categoryPengumumanId: val || "" })}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||||
|
placeholder='Pilih kategori'
|
||||||
|
data={
|
||||||
|
editState.category.findMany.data?.map((v) => ({
|
||||||
|
value: v.id,
|
||||||
|
label: v.name
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
required
|
||||||
|
error={!formData.categoryPengumumanId ? "Pilih kategori" : undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit}>Edit pengumuman</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPengumuman;
|
||||||
@@ -1,40 +1,46 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Box, Button, Paper } from '@mantine/core';
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
|
||||||
function DetailPengumuman() {
|
function DetailPengumuman() {
|
||||||
// const pengumumanState = useProxy(stateDesaPengumuman)
|
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||||
// 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(() => {
|
||||||
// pengumumanState.pengumuman.findUnique.load(params?.id as string)
|
pengumumanState.pengumuman.findUnique.load(params?.id as string)
|
||||||
// }, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
// const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
// if (selectedId) {
|
if (selectedId) {
|
||||||
// pengumumanState.pengumuman.delete.byId(selectedId)
|
pengumumanState.pengumuman.delete.byId(selectedId)
|
||||||
// setModalHapus(false)
|
setModalHapus(false)
|
||||||
// setSelectedId(null)
|
setSelectedId(null)
|
||||||
// router.push("/admin/desa/pengumuman")
|
router.push("/admin/desa/pengumuman/list-pengumuman")
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (!pengumumanState.pengumuman.findUnique.data) {
|
if (!pengumumanState.pengumuman.findUnique.data) {
|
||||||
// return (
|
return (
|
||||||
// <Stack py={10}>
|
<Stack py={10}>
|
||||||
// <Skeleton h={400} />
|
<Skeleton h={400} />
|
||||||
// </Stack>
|
</Stack>
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -44,7 +50,7 @@ function DetailPengumuman() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
{/* <Stack>
|
<Stack>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Pengumuman</Text>
|
<Text fz={"xl"} fw={"bold"}>Detail Pengumuman</Text>
|
||||||
{pengumumanState.pengumuman.findUnique.data ? (
|
{pengumumanState.pengumuman.findUnique.data ? (
|
||||||
<Paper key={pengumumanState.pengumuman.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper key={pengumumanState.pengumuman.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
@@ -79,11 +85,11 @@ function DetailPengumuman() {
|
|||||||
<IconX size={20} />
|
<IconX size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (pengumumanState.pengumuman.findUnique.data) {
|
if (pengumumanState.pengumuman.findUnique.data) {
|
||||||
router.push(`/admin/desa/pengumuman/${pengumumanState.pengumuman.findUnique.data.id}/edit`);
|
router.push(`/admin/desa/pengumuman/list-pengumuman/${pengumumanState.pengumuman.findUnique.data.id}/edit`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!pengumumanState.pengumuman.findUnique.data}
|
disabled={!pengumumanState.pengumuman.findUnique.data}
|
||||||
color={"green"}
|
color={"green"}
|
||||||
>
|
>
|
||||||
@@ -93,16 +99,16 @@ function DetailPengumuman() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack> */}
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus berita ini?'
|
text='Apakah anda yakin ingin menghapus pengumuman ini?'
|
||||||
/> */}
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { Prisma } from '@prisma/client';
|
|
||||||
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';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
|
||||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
|
||||||
|
|
||||||
function CreatePengumuman() {
|
function CreatePengumuman() {
|
||||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
pengumumanState.category.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await pengumumanState.pengumuman.create.create()
|
await pengumumanState.pengumuman.create.create()
|
||||||
resetForm()
|
resetForm()
|
||||||
router.push("/admin/desa/pengumuman")
|
router.push("/admin/desa/pengumuman/list-pengumuman")
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@@ -45,10 +49,21 @@ function CreatePengumuman() {
|
|||||||
pengumumanState.pengumuman.create.form.judul = val.target.value
|
pengumumanState.pengumuman.create.form.judul = val.target.value
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SelectCategory
|
<Select
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||||
|
placeholder='Pilih kategori'
|
||||||
|
data={pengumumanState.category.findMany.data?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id;
|
const selected = pengumumanState.category.findMany.data?.find((item) => item.id === val);
|
||||||
|
if (selected) {
|
||||||
|
pengumumanState.pengumuman.create.form.categoryPengumumanId = selected.id;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
searchable
|
||||||
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
||||||
@@ -76,35 +91,4 @@ function CreatePengumuman() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectCategory({
|
|
||||||
onChange,
|
|
||||||
}: {
|
|
||||||
onChange: (value: Prisma.CategoryPengumumanGetPayload<{ select: { name: true; id: true; } }>) => void;
|
|
||||||
}) {
|
|
||||||
const categoryState = useProxy(stateDesaPengumuman.category);
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
categoryState.findMany.load();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
|
||||||
placeholder='Pilih kategori'
|
|
||||||
data={categoryState.findMany.data?.map((item) => ({
|
|
||||||
label: item.name,
|
|
||||||
value: item.id,
|
|
||||||
}))}
|
|
||||||
onChange={(val) => {
|
|
||||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
|
||||||
if (selected) {
|
|
||||||
onChange(selected);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
searchable
|
|
||||||
nothingFoundMessage="Tidak ditemukan"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreatePengumuman;
|
export default CreatePengumuman;
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } 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';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import stateDesaPengumuman from '../../_state/desa/pengumuman';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||||
|
|
||||||
|
|
||||||
function Pengumuman() {
|
function Pengumuman() {
|
||||||
@@ -16,7 +15,7 @@ function Pengumuman() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Posisi Organisasi'
|
title='List Pengumuman'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -30,31 +29,21 @@ function Pengumuman() {
|
|||||||
function ListPengumuman({ search }: { search: string }) {
|
function ListPengumuman({ search }: { search: string }) {
|
||||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const {
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = pengumumanState.pengumuman.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pengumumanState.pengumuman.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = (data || [])
|
||||||
|
|
||||||
const handleHapus = () => {
|
if (loading || !data) {
|
||||||
if (selectedId) {
|
|
||||||
pengumumanState.pengumuman.delete.byId(selectedId)
|
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredData = (pengumumanState.pengumuman.findMany.data || []).filter(item => {
|
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.CategoryPengumuman?.name.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pengumumanState.pengumuman.findMany.data) {
|
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -71,7 +60,7 @@ function ListPengumuman({ search }: { search: string }) {
|
|||||||
<Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
|
<Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button onClick={() => router.push("/admin/desa/pengumuman/create")} bg={colors['blue-button']}>
|
<Button onClick={() => router.push("/admin/desa/pengumuman/list-pengumuman/create")} bg={colors['blue-button']}>
|
||||||
<IconCircleDashedPlus size={25} />
|
<IconCircleDashedPlus size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
@@ -96,7 +85,7 @@ function ListPengumuman({ search }: { search: string }) {
|
|||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd >{item.CategoryPengumuman?.name}</TableTd>
|
<TableTd >{item.CategoryPengumuman?.name}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button bg={"green"} onClick={() => router.push(`/admin/desa/pengumuman/detail`)}>
|
<Button bg={"green"} onClick={() => router.push(`/admin/desa/pengumuman/list-pengumuman/${item.id}`)}>
|
||||||
<IconDeviceImacCog size={25} />
|
<IconDeviceImacCog size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -107,14 +96,15 @@ function ListPengumuman({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
<Pagination
|
||||||
<ModalKonfirmasiHapus
|
value={page}
|
||||||
opened={modalHapus}
|
onChange={(newPage) => load(newPage)}
|
||||||
onClose={() => setModalHapus(false)}
|
total={totalPages}
|
||||||
onConfirm={handleHapus}
|
mt="md"
|
||||||
text='Apakah anda yakin ingin menghapus berita ini?'
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,11 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
|
|||||||
label: "Profile Perbekel",
|
label: "Profile Perbekel",
|
||||||
value: "profileperbekel",
|
value: "profileperbekel",
|
||||||
href: "/admin/desa/profile/profile-perbekel"
|
href: "/admin/desa/profile/profile-perbekel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Profile Perbekel Dari Masa Ke Masa",
|
||||||
|
value: "profile-perbekel-dari-masa-ke-masa",
|
||||||
|
href: "/admin/desa/profile/profile-perbekel-dari-masa-ke-masa"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function EditPerbekelDariMasaKeMasa() {
|
||||||
|
const state = useProxy(stateProfileDesa.mantanPerbekel)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: state.update.form.nama || '',
|
||||||
|
daerah: state.update.form.daerah || '',
|
||||||
|
periode: state.update.form.periode || '',
|
||||||
|
imageId: state.update.form.imageId || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadFoto = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await state.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama || '',
|
||||||
|
daerah: data.daerah || '',
|
||||||
|
periode: data.periode || '',
|
||||||
|
imageId: data.imageId || ''
|
||||||
|
});
|
||||||
|
if (data?.imageGalleryFoto?.link) {
|
||||||
|
setPreviewImage(data.imageGalleryFoto.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading foto:', error);
|
||||||
|
toast.error('Gagal memuat data foto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadFoto();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
state.update.form = {
|
||||||
|
...state.update.form,
|
||||||
|
nama: formData.nama,
|
||||||
|
daerah: formData.daerah,
|
||||||
|
periode: formData.periode,
|
||||||
|
imageId: formData.imageId
|
||||||
|
};
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
state.update.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
await state.update.update();
|
||||||
|
toast.success('Perbekel dari masa ke masa berhasil diperbarui!');
|
||||||
|
router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating perbekel dari masa ke masa:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui perbekel dari masa ke masa');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Edit Perbekel Dari Masa Ke Masa</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama</Text>}
|
||||||
|
placeholder='Masukkan nama'
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) =>
|
||||||
|
(formData.nama = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text>Upload Foto</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Daerah</Text>}
|
||||||
|
placeholder='Masukkan daerah'
|
||||||
|
value={formData.daerah}
|
||||||
|
onChange={(e) =>
|
||||||
|
(formData.daerah = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Periode</Text>}
|
||||||
|
placeholder='Masukkan periode'
|
||||||
|
value={formData.periode}
|
||||||
|
onChange={(e) =>
|
||||||
|
(formData.periode = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPerbekelDariMasaKeMasa;
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function DetailPerbekelDariMasa() {
|
||||||
|
const state = useProxy(stateProfileDesa.mantanPerbekel)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
state.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Perbekel Dari Masa Ke Masa</Text>
|
||||||
|
{state.findUnique.data ? (
|
||||||
|
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama Perbekel</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Daerah</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.daerah}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Periode</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.periode}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 300, md: 350}} src={state.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
setSelectedId(state.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={state.delete.loading || !state.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/${state.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!state.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus perbekel dari masa ke masa ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPerbekelDariMasa;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
'use client'
|
||||||
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateVideo() {
|
||||||
|
const state = useProxy(stateProfileDesa.mantanPerbekel)
|
||||||
|
const router = useRouter();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
state.create.form = {
|
||||||
|
nama: "",
|
||||||
|
daerah: "",
|
||||||
|
periode: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
setPreviewImage(null)
|
||||||
|
setFile(null)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
state.create.form.imageId = uploaded.id;
|
||||||
|
await state.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Create Perbekel Dari Masa Ke Masa</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Perbekel</Text>}
|
||||||
|
placeholder='Masukkan nama perbekel'
|
||||||
|
value={state.create.form.nama}
|
||||||
|
onChange={(val) => {
|
||||||
|
state.create.form.nama = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Daerah"
|
||||||
|
placeholder="Masukkan daerah"
|
||||||
|
value={state.create.form.daerah}
|
||||||
|
onChange={(e) => {
|
||||||
|
state.create.form.daerah = e.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Periode</Text>}
|
||||||
|
placeholder='Masukkan periode'
|
||||||
|
value={state.create.form.periode}
|
||||||
|
onChange={(e) =>
|
||||||
|
(state.create.form.periode = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateVideo;
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
|
import stateProfileDesa from '../../../_state/desa/profile';
|
||||||
|
|
||||||
|
function PerbekelDariMasaKeMasa() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Perbekel Dari Masa Ke Masa'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListPerbekelDariMasaKeMasa search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
|
||||||
|
const state = useProxy(stateProfileDesa.mantanPerbekel)
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = (data || [])
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Perbekel Dari Masa Ke Masa'
|
||||||
|
href='/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Perbekel</TableTh>
|
||||||
|
<TableTh>Periode</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1}>{item.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
|
||||||
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1}>{item.periode}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PerbekelDariMasaKeMasa;
|
||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
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, Center, FileInput, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -44,21 +45,7 @@ function ProfilePerbekel() {
|
|||||||
perbekelState.findUnique.reset(); // opsional: reset juga data lama
|
perbekelState.findUnique.reset(); // opsional: reset juga data lama
|
||||||
};
|
};
|
||||||
}, [params?.id, router]);
|
}, [params?.id, router]);
|
||||||
|
|
||||||
const handleFileChange = (newFile: File | null) => {
|
|
||||||
if (!newFile) {
|
|
||||||
setFile(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFile(newFile);
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
setPreviewImage(event.target?.result as string);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(newFile);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
|
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
|
||||||
@@ -128,27 +115,61 @@ function ProfilePerbekel() {
|
|||||||
value={perbekelState.edit.form.biodata}
|
value={perbekelState.edit.form.biodata}
|
||||||
onChange={(val) => perbekelState.edit.form.biodata = val}
|
onChange={(val) => perbekelState.edit.form.biodata = val}
|
||||||
/>
|
/>
|
||||||
{/* File Upload */}
|
|
||||||
<FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
accept="image/*"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
<Dropzone
|
||||||
) : (
|
onDrop={(files) => {
|
||||||
<Center w={200} h={200} bg="gray.2">
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
<Stack align="center" gap="xs">
|
if (selectedFile) {
|
||||||
<IconImageInPicture size={48} color="gray" />
|
setFile(selectedFile);
|
||||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
</Stack>
|
}
|
||||||
</Center>
|
}}
|
||||||
)}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function Page() {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
{perbekel && (
|
{perbekel && (
|
||||||
<Box >
|
<Box>
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
@@ -116,14 +116,14 @@ function ListDetailDataPengangguran() {
|
|||||||
{!mounted && !chartData ? (
|
{!mounted && !chartData ? (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
|
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||||
{mounted && chartData.length > 0 && (
|
{mounted && chartData.length > 0 && (
|
||||||
<Box w={{ base: '100%', md: '70%' }}>
|
<Box w={{ base: '100%', md: '70%' }}>
|
||||||
<BarChart
|
<BarChart
|
||||||
|
|||||||
@@ -55,17 +55,17 @@ function EditLowonganKerja() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
lowonganKerjaState.update.form = {
|
// Set the ID for the update
|
||||||
...lowonganKerjaState.update.form,
|
lowonganState.update.id = params?.id as string;
|
||||||
posisi: formData.posisi,
|
|
||||||
namaPerusahaan: formData.namaPerusahaan,
|
// Update the form state
|
||||||
lokasi: formData.lokasi,
|
lowonganState.update.form = {
|
||||||
tipePekerjaan: formData.tipePekerjaan,
|
...lowonganState.update.form,
|
||||||
gaji: formData.gaji,
|
...formData
|
||||||
deskripsi: formData.deskripsi,
|
};
|
||||||
kualifikasi: formData.kualifikasi,
|
|
||||||
}
|
// Call the update function
|
||||||
await lowonganState.update.update()
|
await lowonganState.update.update();
|
||||||
toast.success("Lowongan kerja berhasil diperbarui!");
|
toast.success("Lowongan kerja berhasil diperbarui!");
|
||||||
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -88,7 +88,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.posisi}
|
value={formData.posisi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.posisi = val.target.value;
|
setFormData(prev => ({ ...prev, posisi: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
||||||
placeholder='Masukkan posisi'
|
placeholder='Masukkan posisi'
|
||||||
@@ -96,7 +96,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.namaPerusahaan}
|
value={formData.namaPerusahaan}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.namaPerusahaan = val.target.value;
|
setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
||||||
placeholder='Masukkan nama perusahaan'
|
placeholder='Masukkan nama perusahaan'
|
||||||
@@ -104,7 +104,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.lokasi}
|
value={formData.lokasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.lokasi = val.target.value;
|
setFormData(prev => ({ ...prev, lokasi: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
||||||
placeholder='Masukkan lokasi'
|
placeholder='Masukkan lokasi'
|
||||||
@@ -112,7 +112,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.tipePekerjaan}
|
value={formData.tipePekerjaan}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.tipePekerjaan = val.target.value;
|
setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
||||||
placeholder='Masukkan tipe pekerjaan'
|
placeholder='Masukkan tipe pekerjaan'
|
||||||
@@ -120,7 +120,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.gaji}
|
value={formData.gaji}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.gaji = val.target.value;
|
setFormData(prev => ({ ...prev, gaji: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
||||||
placeholder='Masukkan gaji'
|
placeholder='Masukkan gaji'
|
||||||
@@ -130,7 +130,7 @@ function EditLowonganKerja() {
|
|||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.deskripsi = val;
|
setFormData(prev => ({ ...prev, deskripsi: val }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -139,7 +139,7 @@ function EditLowonganKerja() {
|
|||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.kualifikasi}
|
value={formData.kualifikasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.kualifikasi = val;
|
setFormData(prev => ({ ...prev, kualifikasi: val }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = lowonganState.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
lowonganState.findMany.load();
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (lowonganState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.posisi.toLowerCase().includes(keyword) ||
|
|
||||||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
|
|
||||||
item.lokasi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!lowonganState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Bekerja Sebagai</TableTh>
|
<TableTh>Pekerjaan</TableTh>
|
||||||
<TableTh>Nama Usaha</TableTh>
|
<TableTh>Nama Perusahaan</TableTh>
|
||||||
<TableTh>Alamat Usaha</TableTh>
|
<TableTh>Lokasi</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.posisi}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.namaPerusahaan}</TableTd>
|
<Text fz={"md"}>{item.posisi}</Text>
|
||||||
<TableTd>{item.lokasi}</TableTd>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.namaPerusahaan}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.lokasi}</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
|
|||||||
|
|
||||||
|
|
||||||
function PasarDesa() {
|
function PasarDesa() {
|
||||||
const [search, setSearch] = useState("")
|
const [search2, setSearch2] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Kategori Produk'
|
title='Kategori Produk'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search2}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch2(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListPasarDesa search={search} />
|
<ListPasarDesa search2={search2} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListPasarDesa({ search }: { search: string }) {
|
function ListPasarDesa({ search2 }: { search2: string }) {
|
||||||
const statePasar = useProxy(pasarDesaState.kategoriProduk)
|
const statePasar = useProxy(pasarDesaState.kategoriProduk)
|
||||||
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()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = statePasar.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePasar.findMany.load()
|
load(page, 10, search2)
|
||||||
}, [])
|
}, [page, search2])
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredData = (statePasar.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!statePasar.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -30,21 +30,21 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
const statePasar = useProxy(pasarDesaState.pasarDesa)
|
const statePasar = useProxy(pasarDesaState.pasarDesa)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = statePasar.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePasar.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (statePasar.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword) ||
|
|
||||||
item.harga.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.rating.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.alamatUsaha.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!statePasar.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital';
|
import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital';
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -95,27 +96,61 @@ function EditPenghargaan() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import desaDigitalState from '../../../_state/inovasi/desa-digital';
|
import desaDigitalState from '../../../_state/inovasi/desa-digital';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateDesaDigital() {
|
function CreateDesaDigital() {
|
||||||
@@ -49,7 +50,7 @@ function CreateDesaDigital() {
|
|||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
const success = await stateDesaDigital.create.create()
|
const success = await stateDesaDigital.create.create()
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
resetForm()
|
resetForm()
|
||||||
router.push("/admin/inovasi/desa-digital-smart-village")
|
router.push("/admin/inovasi/desa-digital-smart-village")
|
||||||
@@ -87,25 +88,61 @@ function CreateDesaDigital() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
|
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -94,27 +95,61 @@ function EditInfoTeknologiTepatGuna() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import infoTeknoState from '../../../_state/inovasi/info-tekno';
|
import infoTeknoState from '../../../_state/inovasi/info-tekno';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateInfoTeknologiTepatGuna() {
|
function CreateInfoTeknologiTepatGuna() {
|
||||||
@@ -49,7 +50,7 @@ function CreateInfoTeknologiTepatGuna() {
|
|||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
const success = await stateInfoTekno.create.create()
|
const success = await stateInfoTekno.create.create()
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
resetForm()
|
resetForm()
|
||||||
router.push("/admin/inovasi/info-teknologi-tepat-guna")
|
router.push("/admin/inovasi/info-teknologi-tepat-guna")
|
||||||
@@ -87,25 +88,61 @@ function CreateInfoTeknologiTepatGuna() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ function DetailKeamananLingkungan() {
|
|||||||
{keamananState.findUnique.data ? (
|
{keamananState.findUnique.data ? (
|
||||||
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 490}} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
|
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
|
||||||
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
|
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
|
||||||
@@ -62,10 +66,6 @@ function DetailKeamananLingkungan() {
|
|||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
|
||||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
<Flex gap={"xs"} mt={10}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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 { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,19 +30,21 @@ function ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
const keamananState = useProxy(keamananLingkunganState)
|
const keamananState = useProxy(keamananLingkunganState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = keamananState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
keamananState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (keamananState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!keamananState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -67,9 +69,15 @@ function ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={180}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
|
||||||
@@ -81,6 +89,14 @@ function ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -21,7 +21,7 @@ function PolsekTerdekat() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListPolsekTerdekat search={search}/>
|
<ListPolsekTerdekat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -30,20 +30,21 @@ function ListPolsekTerdekat({ search }: { search: string }) {
|
|||||||
const polsekState = useProxy(polsekTerdekat)
|
const polsekState = useProxy(polsekTerdekat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = polsekState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
polsekState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (polsekState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword) ||
|
|
||||||
item.jarakKeDesa.toLowerCase().includes(keyword) ||
|
|
||||||
item.alamat.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!polsekState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -64,24 +65,44 @@ function ListPolsekTerdekat({ search }: { search: string }) {
|
|||||||
<TableTh>Jarak Polsek</TableTh>
|
<TableTh>Jarak Polsek</TableTh>
|
||||||
<TableTh>Alamat</TableTh>
|
<TableTh>Alamat</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.nama}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.jarakKeDesa}</TableTd>
|
<Box w={180}>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={180}>
|
||||||
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.jarakKeDesa}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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 { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,19 +30,21 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
const stateKeamanan = useProxy(tipsKeamananState)
|
const stateKeamanan = useProxy(tipsKeamananState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateKeamanan.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateKeamanan.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (stateKeamanan.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateKeamanan.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -67,9 +69,15 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.judul}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz={"xs"} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
<Box w={150}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.judul}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
||||||
@@ -81,6 +89,14 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -22,7 +22,7 @@ function ArtikelKesehatan() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListArtikelKesehatan search={search}/>
|
<ListArtikelKesehatan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,45 +46,53 @@ function ListArtikelKesehatan({ search }: { search: string }) {
|
|||||||
if (!stateArtikelKesehatan.findMany.data) {
|
if (!stateArtikelKesehatan.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Skeleton h={500}/>
|
<Skeleton h={500} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Artikel Kesehatan'
|
title='List Artikel Kesehatan'
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
|
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
|
||||||
/>
|
/>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Content</TableTh>
|
<TableTh>Content</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>{item.title}</TableTd>
|
|
||||||
<TableTd>{item.content}</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
|
|
||||||
<IconDeviceImacCog size={25} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.map((item) => (
|
||||||
</Box>
|
<TableTr key={item.id}>
|
||||||
</Stack>
|
<TableTd>
|
||||||
</Paper>
|
<Box w={100}>
|
||||||
</Box>
|
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.title}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.content}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ interface FasilitasKesehatanFormBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function EditFasilitasKesehatan() {
|
function EditFasilitasKesehatan() {
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState);
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Flex, Grid, GridCol, 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';
|
||||||
@@ -12,7 +12,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
function DetailFasilitasKesehatan() {
|
function DetailFasilitasKesehatan() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
@@ -45,9 +45,23 @@ function DetailFasilitasKesehatan() {
|
|||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
|
<Grid>
|
||||||
|
<GridCol span={12}>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={12}>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
|
||||||
|
Tambah Dokter
|
||||||
|
</Button>
|
||||||
|
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/layanan-unggulan/create`)}>
|
||||||
|
Tambah Layanan
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
{stateFasilitasKesehatan.findUnique.data ? (
|
{stateFasilitasKesehatan.findUnique.data ? (
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
@@ -68,6 +82,14 @@ function DetailFasilitasKesehatan() {
|
|||||||
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
|
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
|
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
|
||||||
|
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
|
||||||
|
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
|
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
|
||||||
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
|
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
|
||||||
@@ -77,14 +99,6 @@ function DetailFasilitasKesehatan() {
|
|||||||
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
|
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
|
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
|
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
|
||||||
<Text fz={"md"} fw={"bold"}>Layanan</Text>
|
<Text fz={"md"} fw={"bold"}>Layanan</Text>
|
||||||
@@ -111,6 +125,7 @@ function DetailFasilitasKesehatan() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Hapus */}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
|
|
||||||
function CreateFasilitasKesehatan() {
|
function CreateFasilitasKesehatan() {
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Page
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Page
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function CreateDokter() {
|
||||||
|
const params = useParams()
|
||||||
|
const createState = useProxy(fasilitasKesehatanState.dokter)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
createState.create.create.form = {
|
||||||
|
name: "",
|
||||||
|
specialist: "",
|
||||||
|
jadwal: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await createState.create.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push(`/admin/kesehatan/fasilitas-kesehatan/${params?.id}/dokter-tenaga-medis`)
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box component="form" onSubmit={handleSubmit}>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Create Dokter</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
|
||||||
|
placeholder="masukkan nama dokter"
|
||||||
|
value={createState.create.create.form.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
createState.create.create.form.name = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text fz="md" fw="bold">Specialist</Text>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Specialist</Text>}
|
||||||
|
placeholder="masukkan specialist"
|
||||||
|
value={createState.create.create.form.specialist}
|
||||||
|
onChange={(e) => {
|
||||||
|
createState.create.create.form.specialist = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz="md" fw="bold">Jadwal</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={createState.create.create.form.jadwal}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
createState.create.create.form.jadwal = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateDokter;
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
|
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
||||||
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
function DokterTenagaMedis() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Dokter dan Tenaga Medis'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListDokterTenagaMedis search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListDokterTenagaMedis({ search }: { search: string }) {
|
||||||
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
page,
|
||||||
|
totalPages
|
||||||
|
} = stateFasilitasKesehatan.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Fasilitas Kesehatan'
|
||||||
|
href={`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create`}
|
||||||
|
/>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Fasilitas Kesehatan</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Jam Operasional</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>{item.specialist}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DokterTenagaMedis;
|
||||||
@@ -4,17 +4,39 @@ import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableT
|
|||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
import JudulList from '../../../_com/judulList';
|
||||||
import { useState } from 'react';
|
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
|
|
||||||
|
|
||||||
function FasilitasKesehatan() {
|
function FasilitasKesehatan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
// const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* <Grid>
|
||||||
|
<GridCol span={12}>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Fasilitas Kesehatan'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={12}>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis`)}>
|
||||||
|
<IconList size={20} /> List Dokter
|
||||||
|
</Button>
|
||||||
|
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan`)}>
|
||||||
|
<IconList size={20} /> List Layanan
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</GridCol>
|
||||||
|
</Grid> */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Fasilitas Kesehatan'
|
title='Fasilitas Kesehatan'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
@@ -22,13 +44,13 @@ function FasilitasKesehatan() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListFasilitasKesehatan search={search}/>
|
<ListFasilitasKesehatan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListFasilitasKesehatan({ search }: { search: string }) {
|
function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
@@ -47,47 +69,47 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
|||||||
if (!stateFasilitasKesehatan.findMany.data) {
|
if (!stateFasilitasKesehatan.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Skeleton h={500}/>
|
<Skeleton h={500} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Fasilitas Kesehatan'
|
title='List Fasilitas Kesehatan'
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
|
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
|
||||||
/>
|
/>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Fasilitas Kesehatan</TableTh>
|
<TableTh>Fasilitas Kesehatan</TableTh>
|
||||||
<TableTh>Alamat</TableTh>
|
<TableTh>Dokter</TableTh>
|
||||||
<TableTh>Jam Operasional</TableTh>
|
<TableTh>Layanan</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>{item.informasiumum.alamat}</TableTd>
|
|
||||||
<TableTd>{item.informasiumum.jamOperasional}</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
|
||||||
<IconDeviceImacCog size={25} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.map((item) => (
|
||||||
</Box>
|
<TableTr key={item.id}>
|
||||||
</Stack>
|
<TableTd>{item.name}</TableTd>
|
||||||
</Paper>
|
<TableTd>{item.dokterdantenagamedis.name}</TableTd>
|
||||||
</Box>
|
<TableTd>{item.tarifdanlayanan.layanan}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
|
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
||||||
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
function TarifLayanan() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Dokter dan Tenaga Medis'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListTarifLayanan search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListTarifLayanan({ search }: { search: string }) {
|
||||||
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
page,
|
||||||
|
totalPages
|
||||||
|
} = stateFasilitasKesehatan.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Fasilitas Kesehatan'
|
||||||
|
href={`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create`}
|
||||||
|
/>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Fasilitas Kesehatan</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Jam Operasional</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>{item.specialist}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TarifLayanan;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditGrafikHasilKepuasan() {
|
||||||
|
const editState = useProxy(grafikkepuasan)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: editState.update.form.nama || '',
|
||||||
|
tanggal: editState.update.form.tanggal || '',
|
||||||
|
jenisKelamin: editState.update.form.jenisKelamin || '',
|
||||||
|
alamat: editState.update.form.alamat || '',
|
||||||
|
penyakit: editState.update.form.penyakit || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadKelahiran = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama || '',
|
||||||
|
tanggal: data.tanggal || '',
|
||||||
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
|
alamat: data.alamat || '',
|
||||||
|
penyakit: data.penyakit || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik hasil kepuasan:", error);
|
||||||
|
toast.error("Gagal memuat data grafik hasil kepuasan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadKelahiran();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
editState.update.form = {
|
||||||
|
...editState.update.form,
|
||||||
|
nama: formData.nama,
|
||||||
|
tanggal: formData.tanggal,
|
||||||
|
jenisKelamin: formData.jenisKelamin,
|
||||||
|
alamat: formData.alamat,
|
||||||
|
penyakit: formData.penyakit,
|
||||||
|
};
|
||||||
|
await editState.update.submit();
|
||||||
|
toast.success('grafik hasil kepuasan berhasil diperbarui!');
|
||||||
|
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating grafik hasil kepuasan:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit grafik hasil kepuasan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='date'
|
||||||
|
value={formData.tanggal}
|
||||||
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
||||||
|
placeholder="masukkan tanggal"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.jenisKelamin}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
||||||
|
placeholder="masukkan jenis kelamin"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.penyakit}
|
||||||
|
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Penyakit</Text>}
|
||||||
|
placeholder="masukkan penyakit"
|
||||||
|
/>
|
||||||
|
<Button onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGrafikHasilKepuasan;
|
||||||
@@ -1,80 +1,125 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditGrafikHasilKepuasan() {
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
|
function DetailGrafikHasilKepuasan() {
|
||||||
|
const state = useProxy(grafikkepuasan)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams() as { id: string }
|
|
||||||
const stateGrafikKepuasan = useProxy(grafikkepuasan)
|
|
||||||
|
|
||||||
const id = params.id
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Load data saat komponen mount
|
|
||||||
useEffect(() => {
|
const handleHapus = () => {
|
||||||
if (id) {
|
if (selectedId) {
|
||||||
stateGrafikKepuasan.findUnique.load(id).then(() => {
|
state.delete.byId(selectedId)
|
||||||
const data = stateGrafikKepuasan.findUnique.data
|
setModalHapus(false)
|
||||||
if (data) {
|
setSelectedId(null)
|
||||||
stateGrafikKepuasan.update.form = {
|
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan")
|
||||||
label: data.label || '',
|
|
||||||
jumlah: data.jumlah || '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [id])
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
// Set the ID before submitting
|
|
||||||
stateGrafikKepuasan.update.id = id;
|
|
||||||
await stateGrafikKepuasan.update.submit();
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan')
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={40} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack size={20} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={3}>Edit Grafik Hasil Kepuasan</Title>
|
<Text fz={"xl"} fw={"bold"}>Detail Data Grafik Hasil Kepuasan</Text>
|
||||||
<TextInput
|
{state.findUnique.data ? (
|
||||||
label="Label"
|
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
placeholder="masukkan label"
|
<Stack gap={"xs"}>
|
||||||
value={stateGrafikKepuasan.update.form.label}
|
<Box>
|
||||||
onChange={(val) => {
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
stateGrafikKepuasan.update.form.label = val.currentTarget.value;
|
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
||||||
}}
|
</Box>
|
||||||
/>
|
<Box>
|
||||||
<TextInput
|
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
||||||
label="Jumlah"
|
<Text fz={"lg"}>
|
||||||
type="number"
|
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
|
||||||
placeholder="masukkan jumlah"
|
day: '2-digit',
|
||||||
value={stateGrafikKepuasan.update.form.jumlah}
|
month: 'long',
|
||||||
onChange={(val) => {
|
year: 'numeric'
|
||||||
stateGrafikKepuasan.update.form.jumlah = val.currentTarget.value;
|
})}
|
||||||
}}
|
</Text>
|
||||||
/>
|
</Box>
|
||||||
<Button
|
<Box>
|
||||||
mt={10}
|
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
||||||
bg={colors['blue-button']}
|
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
||||||
onClick={handleSubmit}
|
</Box>
|
||||||
>
|
<Box>
|
||||||
Simpan
|
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
||||||
</Button>
|
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Penyakit</Text>
|
||||||
|
<Text fz={"lg"} >{state.findUnique.data?.penyakit}</Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
setSelectedId(state.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={state.delete.loading || !state.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${state.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!state.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus data ini?'
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditGrafikHasilKepuasan;
|
export default DetailGrafikHasilKepuasan;
|
||||||
@@ -16,20 +16,16 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateGrafikKepuasan.create.form = {
|
stateGrafikKepuasan.create.form = {
|
||||||
label: "",
|
nama: "",
|
||||||
jumlah: "",
|
tanggal: "",
|
||||||
|
jenisKelamin: "",
|
||||||
|
alamat: "",
|
||||||
|
penyakit: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const id = await stateGrafikKepuasan.create.create();
|
await stateGrafikKepuasan.create.create();
|
||||||
if (id) {
|
|
||||||
const idStr = String(id);
|
|
||||||
await stateGrafikKepuasan.findUnique.load(idStr);
|
|
||||||
if (stateGrafikKepuasan.findUnique.data) {
|
|
||||||
setChartData([stateGrafikKepuasan.findUnique.data]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
||||||
}
|
}
|
||||||
@@ -45,21 +41,48 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Label"
|
label="Nama"
|
||||||
type="text"
|
type="text"
|
||||||
value={stateGrafikKepuasan.create.form.label}
|
value={stateGrafikKepuasan.create.form.nama}
|
||||||
placeholder="Masukkan label"
|
placeholder="Masukkan nama"
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
stateGrafikKepuasan.create.form.label = val.currentTarget.value;
|
stateGrafikKepuasan.create.form.nama = val.currentTarget.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Jumlah"
|
label="Tanggal"
|
||||||
type="number"
|
type="date"
|
||||||
value={stateGrafikKepuasan.create.form.jumlah}
|
value={stateGrafikKepuasan.create.form.tanggal}
|
||||||
placeholder="Masukkan jumlah"
|
placeholder="Masukkan tanggal"
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
stateGrafikKepuasan.create.form.jumlah = val.currentTarget.value;
|
stateGrafikKepuasan.create.form.tanggal = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
type="text"
|
||||||
|
value={stateGrafikKepuasan.create.form.jenisKelamin}
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikKepuasan.create.form.jenisKelamin = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
type="text"
|
||||||
|
value={stateGrafikKepuasan.create.form.alamat}
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikKepuasan.create.form.alamat = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Penyakit"
|
||||||
|
type="text"
|
||||||
|
value={stateGrafikKepuasan.create.form.penyakit}
|
||||||
|
placeholder="Masukkan penyakit"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikKepuasan.create.form.penyakit = val.currentTarget.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
/* 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, Title } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
|
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
|
|
||||||
function GrafikHasilKepuasanMasyarakat() {
|
function GrafikHasilKepuasanMasyarakat() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -30,53 +30,79 @@ function GrafikHasilKepuasanMasyarakat() {
|
|||||||
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
||||||
type PDKMGrafik = {
|
type PDKMGrafik = {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
nama: string;
|
||||||
jumlah: number;
|
tanggal: string | Date; // Allow both string and Date types
|
||||||
|
jenisKelamin: string;
|
||||||
|
alamat: string;
|
||||||
|
penyakit: string;
|
||||||
|
createdAt?: Date; // Add optional fields that might come from the API
|
||||||
|
updatedAt?: Date;
|
||||||
|
deletedAt?: Date | null;
|
||||||
}
|
}
|
||||||
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
||||||
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleDelete = () => {
|
const {
|
||||||
if (selectedId) {
|
data,
|
||||||
stateGrafikKepuasan.delete.byId(selectedId)
|
page,
|
||||||
setModalHapus(false)
|
totalPages,
|
||||||
setSelectedId(null)
|
loading,
|
||||||
|
load
|
||||||
stateGrafikKepuasan.findMany.load()
|
} = stateGrafikKepuasan.findMany;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
stateGrafikKepuasan.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
if (stateGrafikKepuasan.findMany.data) {
|
if (data) {
|
||||||
setChartData(stateGrafikKepuasan.findMany.data.map((item) => ({
|
setChartData(data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label,
|
nama: item.nama,
|
||||||
jumlah: Number(item.jumlah),
|
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal,
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
penyakit: item.penyakit,
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
}, [stateGrafikKepuasan.findMany.data]);
|
}, [data]);
|
||||||
|
|
||||||
const filteredData = (stateGrafikKepuasan.findMany.data || []).filter(item => {
|
// Add this function to process the data
|
||||||
const keyword = search.toLowerCase();
|
const processDiseaseData = (data: PDKMGrafik[]) => {
|
||||||
return (
|
const diseaseCount: Record<string, number> = {};
|
||||||
item.label.toLowerCase().includes(keyword) ||
|
|
||||||
item.jumlah.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateGrafikKepuasan.findMany.data) {
|
data.forEach(item => {
|
||||||
|
const penyakit = item.penyakit.trim();
|
||||||
|
if (penyakit) {
|
||||||
|
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.entries(diseaseCount).map(([name, count]) => ({
|
||||||
|
name,
|
||||||
|
count
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add this state to store the processed chart data
|
||||||
|
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
|
||||||
|
|
||||||
|
// Update the chart data when data changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
setDiseaseChartData(processDiseaseData(data));
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -89,40 +115,36 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
{/* Form Input */}
|
{/* Form Input */}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
title='List Grafik Hasil Kepuasan Masyarakat'
|
title='List Grafik Hasil Kepuasan Masyarakat'
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
|
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Label</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Jumlah</TableTh>
|
<TableTh>Tanggal</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh>Penyakit</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.label}</TableTd>
|
<TableTd>{item.nama}</TableTd>
|
||||||
<TableTd>{item.jumlah}</TableTd>
|
<TableTd>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
|
<TableTd>{item.penyakit}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`)}>
|
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`)}>
|
||||||
<IconEdit size={20} />
|
<IconDeviceImacCog size={20} />
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={stateGrafikKepuasan.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -130,6 +152,15 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
@@ -141,30 +172,29 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
||||||
<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 && chartData.length > 0 && (
|
{mounted && diseaseChartData.length > 0 && (
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
|
||||||
<XAxis dataKey="label" />
|
<XAxis
|
||||||
|
dataKey="name"
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
interval={0}
|
||||||
|
angle={-45}
|
||||||
|
textAnchor="end"
|
||||||
|
height={70}
|
||||||
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
|
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
|
||||||
|
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
function EditPersentaseDataKelahiranKematian() {
|
|
||||||
const router = useRouter()
|
|
||||||
const params = useParams() as { id: string }
|
|
||||||
const statePresentase = useProxy(persentasekelahiran)
|
|
||||||
|
|
||||||
const id = params.id
|
|
||||||
|
|
||||||
// Load data saat komponen mount
|
|
||||||
// Di file page.tsx, ubah useEffect-nya menjadi:
|
|
||||||
useEffect(() => {
|
|
||||||
if (!id) return;
|
|
||||||
statePresentase.update.id = id;
|
|
||||||
statePresentase.findUnique.load(id)
|
|
||||||
.then(() => {
|
|
||||||
const data = statePresentase.findUnique.data;
|
|
||||||
if (data) {
|
|
||||||
statePresentase.update.form = {
|
|
||||||
tahun: String(data.tahun || ''),
|
|
||||||
kematianKasar: String(data.kematianKasar || ''),
|
|
||||||
kelahiranKasar: String(data.kelahiranKasar || ''),
|
|
||||||
kematianBayi: String(data.kematianBayi || '')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
toast.error('Gagal memuat data');
|
|
||||||
});
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
// Di handleSubmit, ubah menjadi:
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
statePresentase.update.id = id;
|
|
||||||
await statePresentase.update.submit();
|
|
||||||
toast.success('Data berhasil diperbarui');
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating data:', error);
|
|
||||||
toast.error('Gagal memperbarui data');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack size={20} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Edit Persentase Data Kelahiran & Kematian</Title>
|
|
||||||
<TextInput
|
|
||||||
label="Tahun"
|
|
||||||
placeholder="masukkan tahun"
|
|
||||||
value={statePresentase.update.form.tahun}
|
|
||||||
onChange={(val) => {
|
|
||||||
statePresentase.update.form.tahun = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kematian Kasar"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan kematian kasar"
|
|
||||||
value={statePresentase.update.form.kematianKasar}
|
|
||||||
onChange={(val) => {
|
|
||||||
statePresentase.update.form.kematianKasar = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kematian Bayi"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan kematian bayi"
|
|
||||||
value={statePresentase.update.form.kematianBayi}
|
|
||||||
onChange={(val) => {
|
|
||||||
statePresentase.update.form.kematianBayi = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kelahiran Kasar"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan kelahiran kasar"
|
|
||||||
value={statePresentase.update.form.kelahiranKasar}
|
|
||||||
onChange={(val) => {
|
|
||||||
statePresentase.update.form.kelahiranKasar = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Simpan Perubahan
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPersentaseDataKelahiranKematian;
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
'use client'
|
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
function CreatePersentaseDataKelahiranKematian() {
|
|
||||||
const statePersentase = useProxy(persentasekelahiran);
|
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
statePersentase.create.form = {
|
|
||||||
tahun: "",
|
|
||||||
kematianBayi: "",
|
|
||||||
kematianKasar: "",
|
|
||||||
kelahiranKasar: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
const id = await statePersentase.create.create();
|
|
||||||
if (id) {
|
|
||||||
const idStr = String(id);
|
|
||||||
await statePersentase.findUnique.load(idStr);
|
|
||||||
if (statePersentase.findUnique.data) {
|
|
||||||
setChartData([statePersentase.findUnique.data]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian");
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack size={20} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title order={4}>Tambah Persentase Data Kelahiran & Kematian</Title>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<TextInput
|
|
||||||
label="Tahun"
|
|
||||||
type="number"
|
|
||||||
value={statePersentase.create.form.tahun}
|
|
||||||
placeholder="Masukkan tahun"
|
|
||||||
onChange={(val) => {
|
|
||||||
statePersentase.create.form.tahun = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kematian Kasar"
|
|
||||||
type="number"
|
|
||||||
value={statePersentase.create.form.kematianKasar}
|
|
||||||
placeholder="Masukkan kematian kasar"
|
|
||||||
onChange={(val) => {
|
|
||||||
statePersentase.create.form.kematianKasar = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kematian Bayi"
|
|
||||||
type="number"
|
|
||||||
value={statePersentase.create.form.kematianBayi}
|
|
||||||
placeholder="Masukkan kematian bayi"
|
|
||||||
onChange={(val) => {
|
|
||||||
statePersentase.create.form.kematianBayi = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Kelahiran Kasar"
|
|
||||||
type="number"
|
|
||||||
value={statePersentase.create.form.kelahiranKasar}
|
|
||||||
placeholder="Masukkan kelahiran kasar"
|
|
||||||
onChange={(val) => {
|
|
||||||
statePersentase.create.form.kelahiranKasar = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
mt={10}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreatePersentaseDataKelahiranKematian;
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditKelahiran() {
|
||||||
|
const editState = useProxy(persentaseKelahiranKematian.kelahiran)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: editState.edit.form.nama || '',
|
||||||
|
tanggal: editState.edit.form.tanggal || '',
|
||||||
|
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
||||||
|
alamat: editState.edit.form.alamat || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadKelahiran = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama || '',
|
||||||
|
tanggal: data.tanggal || '',
|
||||||
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
|
alamat: data.alamat || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data kelahiran:", error);
|
||||||
|
toast.error("Gagal memuat data data kelahiran");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadKelahiran();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
editState.edit.form = {
|
||||||
|
...editState.edit.form,
|
||||||
|
nama: formData.nama,
|
||||||
|
tanggal: formData.tanggal,
|
||||||
|
jenisKelamin: formData.jenisKelamin,
|
||||||
|
alamat: formData.alamat,
|
||||||
|
};
|
||||||
|
await editState.edit.update();
|
||||||
|
toast.success('data kelahiran berhasil diperbarui!');
|
||||||
|
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating data kelahiran:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit data kelahiran</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='date'
|
||||||
|
value={formData.tanggal}
|
||||||
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
||||||
|
placeholder="masukkan tanggal"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.jenisKelamin}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
||||||
|
placeholder="masukkan jenis kelamin"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
/>
|
||||||
|
<Button onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditKelahiran;
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
'use client'
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
|
function DetailKelahiran() {
|
||||||
|
const state = useProxy(persentaseKelahiranKematian.kelahiran)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
state.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={40} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Data Kelahiran</Text>
|
||||||
|
{state.findUnique.data ? (
|
||||||
|
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
||||||
|
<Text fz={"lg"}>
|
||||||
|
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
||||||
|
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
||||||
|
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
setSelectedId(state.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={state.delete.loading || !state.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${state.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!state.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus data ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailKelahiran;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
'use client'
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateKelahiran() {
|
||||||
|
const createState = useProxy(persentaseKelahiranKematian.kelahiran)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
createState.create.form = {
|
||||||
|
nama: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelamin: "",
|
||||||
|
alamat: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await createState.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Create Kelahiran</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama</Text>}
|
||||||
|
placeholder='Masukkan nama'
|
||||||
|
value={createState.create.form.nama}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.nama = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='date'
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
||||||
|
placeholder='Masukkan tanggal'
|
||||||
|
value={createState.create.form.tanggal}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.tanggal = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Jenis Kelamin</Text>}
|
||||||
|
placeholder='Masukkan jenis kelamin'
|
||||||
|
value={createState.create.form.jenisKelamin}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.jenisKelamin = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Alamat</Text>}
|
||||||
|
placeholder='Masukkan alamat'
|
||||||
|
value={createState.create.form.alamat}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.alamat = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateKelahiran;
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
'use client'
|
||||||
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
|
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
||||||
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function Kelahiran() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Data Kelahiran'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListKelahiran search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ListKelahiran({ search }: { search: string }) {
|
||||||
|
const statePersentase = useProxy(persentasekelahiran.kelahiran);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load
|
||||||
|
} = statePersentase.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
{/* Form Input */}
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Data Kelahiran'
|
||||||
|
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Tanggal</TableTh>
|
||||||
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.nama}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
|
<TableTd>{item.alamat}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Kelahiran;
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditKematian() {
|
||||||
|
const editState = useProxy(persentaseKelahiranKematian.kematian)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: editState.edit.form.nama || '',
|
||||||
|
tanggal: editState.edit.form.tanggal || '',
|
||||||
|
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
||||||
|
alamat: editState.edit.form.alamat || '',
|
||||||
|
penyebab: editState.edit.form.penyebab || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadKelahiran = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama || '',
|
||||||
|
tanggal: data.tanggal || '',
|
||||||
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
|
alamat: data.alamat || '',
|
||||||
|
penyebab: data.penyebab || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data kelahiran:", error);
|
||||||
|
toast.error("Gagal memuat data data kelahiran");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadKelahiran();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
editState.edit.form = {
|
||||||
|
...editState.edit.form,
|
||||||
|
nama: formData.nama,
|
||||||
|
tanggal: formData.tanggal,
|
||||||
|
jenisKelamin: formData.jenisKelamin,
|
||||||
|
alamat: formData.alamat,
|
||||||
|
penyebab: formData.penyebab,
|
||||||
|
};
|
||||||
|
await editState.edit.update();
|
||||||
|
toast.success('data kelahiran berhasil diperbarui!');
|
||||||
|
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating data kelahiran:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit data kelahiran</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='date'
|
||||||
|
value={formData.tanggal}
|
||||||
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
||||||
|
placeholder="masukkan tanggal"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.jenisKelamin}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
||||||
|
placeholder="masukkan jenis kelamin"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Penyebab</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.penyebab}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, penyebab: htmlContent }));
|
||||||
|
editState.edit.form.penyebab = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditKematian;
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
'use client'
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
|
function DetailKematian() {
|
||||||
|
const state = useProxy(persentaseKelahiranKematian.kematian)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
state.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={40} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Data Kematian</Text>
|
||||||
|
{state.findUnique.data ? (
|
||||||
|
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
||||||
|
<Text fz={"lg"}>
|
||||||
|
{state.findUnique.data?.tanggal instanceof Date
|
||||||
|
? state.findUnique.data.tanggal.toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
: state.findUnique.data?.tanggal}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
||||||
|
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
||||||
|
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Penyebab</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.penyebab || '' }} />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
setSelectedId(state.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={state.delete.loading || !state.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (state.findUnique.data) {
|
||||||
|
router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${state.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!state.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus data ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailKematian;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateKematian() {
|
||||||
|
const createState = useProxy(persentaseKelahiranKematian.kematian)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
createState.create.form = {
|
||||||
|
nama: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelamin: "",
|
||||||
|
alamat: "",
|
||||||
|
penyebab: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await createState.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian")
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Create Kematian</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama</Text>}
|
||||||
|
placeholder='Masukkan nama'
|
||||||
|
value={createState.create.form.nama}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.nama = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='date'
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
||||||
|
placeholder='Masukkan tanggal'
|
||||||
|
value={createState.create.form.tanggal}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.tanggal = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Jenis Kelamin</Text>}
|
||||||
|
placeholder='Masukkan jenis kelamin'
|
||||||
|
value={createState.create.form.jenisKelamin}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.jenisKelamin = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Alamat</Text>}
|
||||||
|
placeholder='Masukkan alamat'
|
||||||
|
value={createState.create.form.alamat}
|
||||||
|
onChange={(val) => {
|
||||||
|
createState.create.form.alamat = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Penyebab</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={createState.create.form.penyebab}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
createState.create.form.penyebab = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateKematian;
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
'use client'
|
||||||
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
|
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
||||||
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function Kematian() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Data Kematian'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListKematian search={search} />
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ListKematian({ search }: { search: string }) {
|
||||||
|
const statePersentase = useProxy(persentasekelahiran.kematian);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load
|
||||||
|
} = statePersentase.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
{/* Form Input */}
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Data Kematian'
|
||||||
|
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Tanggal</TableTh>
|
||||||
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.nama}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
|
<TableTd>{item.alamat}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Kematian;
|
||||||
@@ -1,183 +1,227 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { ActionIcon, Box, Center, Flex, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
import { Bar, BarChart, Legend, Tooltip, TooltipProps, XAxis, YAxis } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
type TooltipPayload = {
|
||||||
import HeaderSearch from '../../../_com/header';
|
name: string;
|
||||||
|
value: number;
|
||||||
|
payload: any;
|
||||||
|
color: string;
|
||||||
|
dataKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomTooltipProps = TooltipProps<number, string> & {
|
||||||
|
active?: boolean;
|
||||||
|
payload?: TooltipPayload[];
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
function PersentaseDataKelahiranKematian() {
|
function PersentaseDataKelahiranKematian() {
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack gap={"xs"}>
|
||||||
<HeaderSearch
|
<GrafikPersentaseKelahiranKematian />
|
||||||
title='Persentase Data Kelahiran & Kematian'
|
</Stack>
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<ListPersentaseDataKelahiranKematian search={search} />
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function ListPersentaseDataKelahiranKematian({ search }: { search: string }) {
|
function GrafikPersentaseKelahiranKematian() {
|
||||||
type PDKMGrafik = {
|
|
||||||
id: string;
|
|
||||||
tahun: string;
|
|
||||||
kematianKasar: number;
|
|
||||||
kematianBayi: number;
|
|
||||||
kelahiranKasar: number;
|
|
||||||
}
|
|
||||||
const statePersentase = useProxy(persentasekelahiran);
|
|
||||||
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
type DataTahunan = {
|
||||||
|
tahun: string;
|
||||||
|
totalKelahiran: number;
|
||||||
|
totalKematian: number;
|
||||||
|
data: Array<{
|
||||||
|
id: string;
|
||||||
|
bulan: string;
|
||||||
|
kelahiran: number;
|
||||||
|
kematian: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
// Count occurrences per year
|
||||||
if (selectedId) {
|
const countByYear = (data: any[], dateField: string) => {
|
||||||
statePersentase.delete.byId(selectedId)
|
const counts: Record<string, number> = {};
|
||||||
setModalHapus(false)
|
data?.forEach(item => {
|
||||||
setSelectedId(null)
|
const year = new Date(item[dateField]).getFullYear().toString();
|
||||||
|
counts[year] = (counts[year] || 0) + 1;
|
||||||
|
});
|
||||||
|
return counts;
|
||||||
|
};
|
||||||
|
|
||||||
statePersentase.findMany.load()
|
const statePersentase = useProxy(persentasekelahiran);
|
||||||
|
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
||||||
|
const isTablet = useMediaQuery('(max-width: 1024px)');
|
||||||
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
|
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Format number to Indonesian locale
|
||||||
|
const formatNumber = (num: number) => {
|
||||||
|
return new Intl.NumberFormat('id-ID').format(num);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format tooltip
|
||||||
|
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<Paper p="md" shadow="md" withBorder>
|
||||||
|
<Text size="sm" fw={500} mb={5}>Tahun {label}</Text>
|
||||||
|
<Text size="sm" c="blue">Kelahiran: {formatNumber(payload[0].value)}</Text>
|
||||||
|
<Text size="sm" c="red">Kematian: {formatNumber(payload[1].value)}</Text>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
|
||||||
statePersentase.findMany.load()
|
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
||||||
if (statePersentase.findMany.data) {
|
// Count kelahiran and kematian by year
|
||||||
setChartData(statePersentase.findMany.data.map((item) => ({
|
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
|
||||||
id: item.id,
|
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
|
||||||
tahun: item.tahun,
|
|
||||||
kematianKasar: Number(item.kematianKasar),
|
// Get all unique years
|
||||||
kematianBayi: Number(item.kematianBayi),
|
const allYears = new Set([
|
||||||
kelahiranKasar: Number(item.kelahiranKasar),
|
...Object.keys(kelahiranByYear),
|
||||||
})));
|
...Object.keys(kematianByYear)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create data structure for the chart
|
||||||
|
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
|
||||||
|
acc[year] = {
|
||||||
|
tahun: year,
|
||||||
|
totalKelahiran: kelahiranByYear[year] || 0,
|
||||||
|
totalKematian: kematianByYear[year] || 0,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const sortedData = Object.values(dataByYear).sort((a, b) =>
|
||||||
|
parseInt(a.tahun) - parseInt(b.tahun)
|
||||||
|
);
|
||||||
|
|
||||||
|
setChartData(sortedData);
|
||||||
|
setSelectedYear(sortedData[0]?.tahun || '');
|
||||||
}
|
}
|
||||||
}, [statePersentase.findMany.data]);
|
}, [
|
||||||
|
statePersentase.kelahiran.findMany.data,
|
||||||
|
statePersentase.kematian.findMany.data,
|
||||||
|
]);
|
||||||
|
|
||||||
const filteredData = (statePersentase.findMany.data || []).filter(item => {
|
if (!statePersentase.kelahiran.findMany.data || !statePersentase.kematian.findMany.data) {
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.tahun.toLowerCase().includes(keyword) ||
|
|
||||||
item.kematianKasar.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.kematianBayi.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.kelahiranKasar.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!statePersentase.findMany.data) {
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedYearData = chartData.find(d => d.tahun === selectedYear);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Paper bg={colors['white-1']} p="md">
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
{/* Form Input */}
|
<Title order={3} mb="md">Statistik Kelahiran & Kematian</Title>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Box>
|
||||||
<JudulListTab
|
<Flex gap={"xs"}>
|
||||||
title='List Persentase Data Kelahiran & Kematian'
|
<Box>
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/create'
|
<ActionIcon size={30} color={colors['blue-button']} onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran')}>
|
||||||
placeholder='pencarian'
|
<IconBabyCarriage size={30} color={colors['white-1']} />
|
||||||
searchIcon={<IconSearch size={16} />}
|
</ActionIcon>
|
||||||
/>
|
</Box>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Box>
|
||||||
<TableThead>
|
<ActionIcon size={30} color={colors['blue-button']} onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian')} >
|
||||||
<TableTr>
|
<IconGrave2 size={30} color={colors['white-1']} />
|
||||||
<TableTh>Tahun</TableTh>
|
</ActionIcon>
|
||||||
<TableTh>Kematian Kasar</TableTh>
|
</Box>
|
||||||
<TableTh>Kematian Bayi</TableTh>
|
</Flex>
|
||||||
<TableTh>kelahiran Kasar</TableTh>
|
</Box>
|
||||||
<TableTh>Edit</TableTh>
|
|
||||||
<TableTh>Delete</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>{item.tahun}</TableTd>
|
|
||||||
<TableTd>{item.kematianKasar}</TableTd>
|
|
||||||
<TableTd>{item.kematianBayi}</TableTd>
|
|
||||||
<TableTd>{item.kelahiranKasar}</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/${item.id}`)}>
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={statePersentase.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
|
||||||
))}
|
|
||||||
</TableTbody>
|
|
||||||
</Table>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
{!mounted && !chartData ? (
|
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
|
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
|
||||||
{mounted && chartData.length > 0 && (
|
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
|
||||||
<XAxis dataKey="tahun" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
|
|
||||||
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
|
|
||||||
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
|
|
||||||
</BarChart>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{chartData.length === 0 ? (
|
||||||
<ModalKonfirmasiHapus
|
<Text c="dimmed" ta="center" py="xl">
|
||||||
opened={modalHapus}
|
Belum ada data yang tersedia untuk ditampilkan
|
||||||
onClose={() => setModalHapus(false)}
|
</Text>
|
||||||
onConfirm={handleDelete}
|
) : (
|
||||||
text='Apakah anda yakin ingin menghapus persentase data kelahiran & kematian ini?'
|
<>
|
||||||
/>
|
{/* Year Selector */}
|
||||||
</Box>
|
<Box mb="md" style={{ maxWidth: '200px' }}>
|
||||||
|
<Select
|
||||||
|
label="Pilih Tahun"
|
||||||
|
placeholder="Pilih Tahun"
|
||||||
|
data={chartData.map((item) => ({
|
||||||
|
value: item.tahun,
|
||||||
|
label: item.tahun
|
||||||
|
}))}
|
||||||
|
value={selectedYear}
|
||||||
|
onChange={(value) => setSelectedYear(value || '')}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Main Chart */}
|
||||||
|
<Center>
|
||||||
|
<Box h={400}>
|
||||||
|
<BarChart
|
||||||
|
width={isMobile ? window.innerWidth * 0.9 : isTablet ? 700 : 800}
|
||||||
|
height={350}
|
||||||
|
data={chartData}
|
||||||
|
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
|
||||||
|
>
|
||||||
|
<XAxis dataKey="tahun" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip content={<CustomTooltip />} />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="totalKelahiran" name="Total Kelahiran" fill="#4dabf7" />
|
||||||
|
<Bar dataKey="totalKematian" name="Total Kematian" fill="#f03e3e" />
|
||||||
|
</BarChart>
|
||||||
|
</Box>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{/* Yearly Breakdown */}
|
||||||
|
{selectedYearData && (
|
||||||
|
<Box mt="xl">
|
||||||
|
<Title order={4} mb="md">Rincian Tahun {selectedYear}</Title>
|
||||||
|
<Table striped withTableBorder>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Bulan</Table.Th>
|
||||||
|
<Table.Th ta="right">Kelahiran</Table.Th>
|
||||||
|
<Table.Th ta="right">Kematian</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{selectedYearData.data.map((item) => (
|
||||||
|
<Table.Tr key={item.id}>
|
||||||
|
<Table.Td>{item.bulan}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(item.kelahiran)}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(item.kematian)}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
<Table.Tr style={{ fontWeight: 'bold' }}>
|
||||||
|
<Table.Td>Total</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(selectedYearData.totalKelahiran)}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(selectedYearData.totalKematian)}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -91,57 +92,91 @@ function EditInfoWabahPenyakit() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
value={formData.deskripsiSingkat}
|
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
|
||||||
placeholder="masukkan deskripsi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FileInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
value={formData.deskripsiSingkat}
|
||||||
value={file}
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
onChange={async (e) => {
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
if (!e) return;
|
placeholder="masukkan deskripsi"
|
||||||
setFile(e);
|
/>
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
) : (
|
<EditEditor
|
||||||
<Center w={200} h={200} bg="gray">
|
value={formData.deskripsi}
|
||||||
<IconImageInPicture />
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
</Center>
|
/>
|
||||||
)}
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<div>
|
||||||
Simpan
|
<Text size="xl" inline>
|
||||||
</Button>
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Stack>
|
</Text>
|
||||||
</Paper>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
function CreateInfoWabahPenyakit() {
|
function CreateInfoWabahPenyakit() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -94,28 +95,62 @@ function CreateInfoWabahPenyakit() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
{/* Tampilkan preview kalau ada */}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
{previewImage && (
|
||||||
) : (
|
<Box mt="sm">
|
||||||
<Center w={200} h={200} bg="gray">
|
<Image
|
||||||
<IconImageInPicture />
|
src={previewImage}
|
||||||
</Center>
|
alt="Preview"
|
||||||
)}
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
@@ -21,7 +21,7 @@ function InfoWabahPenyakit() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListInfoWabahPenyakit search={search}/>
|
<ListInfoWabahPenyakit search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -30,19 +30,21 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
|||||||
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = infoWabahPenyakitState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
infoWabahPenyakitState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (infoWabahPenyakitState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsiSingkat.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!infoWabahPenyakitState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -51,49 +53,60 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Info Wabah Penyakit'
|
title='List Info Wabah Penyakit'
|
||||||
href='/admin/kesehatan/info-wabah-penyakit/create'
|
href='/admin/kesehatan/info-wabah-penyakit/create'
|
||||||
/>
|
/>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Deskripsi Singkat</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>
|
|
||||||
<Box w={100}>
|
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
|
|
||||||
<IconDeviceImacCog size={25} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.map((item) => (
|
||||||
</Box>
|
<TableTr key={item.id}>
|
||||||
</Stack>
|
<TableTd>
|
||||||
</Paper>
|
<Box w={100}>
|
||||||
</Box>
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
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, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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';
|
||||||
@@ -89,49 +90,82 @@ function EditKontakDarurat() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Kontak Darurat</Title>
|
<Title order={3}>Edit Kontak Darurat</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
{/* Tampilkan preview kalau ada */}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
{previewImage && (
|
||||||
) : (
|
<Box mt="sm">
|
||||||
<Center w={200} h={200} bg="gray">
|
<Image
|
||||||
<IconImageInPicture />
|
src={previewImage}
|
||||||
</Center>
|
alt="Preview"
|
||||||
)}
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
maxHeight: '200px',
|
||||||
Simpan
|
objectFit: 'contain',
|
||||||
</Button>
|
borderRadius: '8px',
|
||||||
</Stack>
|
border: '1px solid #ddd',
|
||||||
</Paper>
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,113 +1,147 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateKontakDarurat() {
|
function CreateKontakDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
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);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
kontakDaruratState.create.form = {
|
kontakDaruratState.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
};
|
|
||||||
setPreviewImage(null);
|
|
||||||
setFile(null);
|
|
||||||
};
|
};
|
||||||
|
setPreviewImage(null);
|
||||||
const handleSubmit = async () => {
|
setFile(null);
|
||||||
if (!file) {
|
};
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
|
||||||
}
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
file,
|
|
||||||
name: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
kontakDaruratState.create.form.imageId = uploaded.id;
|
|
||||||
|
|
||||||
await kontakDaruratState.create.create();
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/kesehatan/kontak-darurat")
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Box>
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
<Box mb={10}>
|
file,
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
name: file.name,
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
});
|
||||||
</Button>
|
|
||||||
</Box>
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
return toast.error("Gagal upload gambar");
|
||||||
<Stack gap="xs">
|
}
|
||||||
<Title order={3}>Create Kontak Darurat</Title>
|
|
||||||
|
kontakDaruratState.create.form.imageId = uploaded.id;
|
||||||
<TextInput
|
|
||||||
value={kontakDaruratState.create.form.name}
|
await kontakDaruratState.create.create();
|
||||||
onChange={(val) => {
|
|
||||||
kontakDaruratState.create.form.name = val.target.value;
|
resetForm();
|
||||||
}}
|
router.push("/admin/kesehatan/kontak-darurat")
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
}
|
||||||
placeholder="masukkan judul"
|
return (
|
||||||
/>
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
<Box>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
<CreateEditor
|
</Button>
|
||||||
value={kontakDaruratState.create.form.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
kontakDaruratState.create.form.deskripsi = val;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={async (e) => {
|
|
||||||
if (!e) return;
|
|
||||||
setFile(e);
|
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Create Kontak Darurat</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={kontakDaruratState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={kontakDaruratState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKontakDarurat;
|
export default CreateKontakDarurat;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
@@ -21,7 +21,7 @@ function KontakDarurat() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListKontakDarurat search={search}/>
|
<ListKontakDarurat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -30,19 +30,21 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = kontakDaruratState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
kontakDaruratState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (kontakDaruratState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!kontakDaruratState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -77,7 +79,9 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={100}>
|
||||||
|
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
@@ -94,6 +98,15 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</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