feat(kesehatan): tambah relasi banjar ke penderita penyakit dan CRUD banjar admin
- Tambah field banjarId (optional) ke GrafikKepuasan + migration - API CRUD banjar baru di /api/desa/banjar/* - Update API grafik_kepuasan: create, find-many (filter by banjar), findUnique, updt - semua include banjar - State: tambah banjarId ke form, banjarList proxy, filter by banjarId - UI list: kolom Banjar di tabel desktop, kartu mobile, Select filter by banjar - UI create/edit: Select banjar (opsional), load banjarList on mount - UI detail: tampilkan field Banjar - Admin banjar: halaman list, create, detail - Sidebar: menu Banjar di domain Desa Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
66
MIND/SUMMARY/banjar-grafik-kepuasan-integration-summary.md
Normal file
66
MIND/SUMMARY/banjar-grafik-kepuasan-integration-summary.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Summary: Banjar Integration untuk Penderita Penyakit (GrafikKepuasan)
|
||||||
|
|
||||||
|
**Tanggal:** 2026-05-30
|
||||||
|
**Branch:** `tasks/kesehatan/banjar-penderita-penyakit/20260530`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Apa yang Dikerjakan
|
||||||
|
|
||||||
|
Menambahkan relasi **Banjar** ke modul Penderita Penyakit (model `GrafikKepuasan`) secara end-to-end: dari schema database, API, state management, hingga UI admin.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perubahan per Layer
|
||||||
|
|
||||||
|
### 1. Database — `prisma/schema.prisma` + Migration
|
||||||
|
- Tambah field `banjarId String?` (optional) ke model `GrafikKepuasan`
|
||||||
|
- Tambah relasi ke model `Banjar` dengan `onDelete: SetNull`
|
||||||
|
- Migration: `20260530025023_add_banjar_to_grafik_kepuasan`
|
||||||
|
|
||||||
|
### 2. API Banjar (baru) — `src/app/api/[[...slugs]]/_lib/desa/banjar/`
|
||||||
|
- CRUD lengkap: `create.ts`, `findMany.ts`, `findUnique.ts`, `updt.ts`, `del.ts`, `index.ts`
|
||||||
|
- Didaftarkan di `src/app/api/[[...slugs]]/_lib/desa/index.ts`
|
||||||
|
- Endpoint: `/api/desa/banjar/*`
|
||||||
|
|
||||||
|
### 3. API GrafikKepuasan (update)
|
||||||
|
- `create.ts` — terima `banjarId` opsional, include `banjar` di response
|
||||||
|
- `find-many.ts` — filter by `banjarId` via query param, include `banjar`
|
||||||
|
- `findUnique.ts` — include `banjar` di response
|
||||||
|
- `updt.ts` — terima dan simpan `banjarId`, include `banjar`
|
||||||
|
- `index.ts` — daftarkan route yang diperlukan
|
||||||
|
|
||||||
|
### 4. State — `grafikKepuasan.ts`
|
||||||
|
- Tambah `banjarId` ke `defaultForm`, schema Zod, `create.form`, `update.form`
|
||||||
|
- Tambah `banjarList` proxy: fetch dari `/api/desa/banjar/findMany?limit=100`
|
||||||
|
- `findMany.load()` terima parameter `banjarId` untuk filter
|
||||||
|
- Type `findMany.data` update ke include `banjar: { id, name }`
|
||||||
|
|
||||||
|
### 5. State Banjar (baru) — `src/app/admin/(dashboard)/_state/desa/banjar.ts`
|
||||||
|
- Proxy Valtio untuk CRUD Banjar (create, findMany, findUnique, update, delete)
|
||||||
|
|
||||||
|
### 6. Admin UI Banjar (baru) — `src/app/admin/(dashboard)/desa/banjar/`
|
||||||
|
- `page.tsx` — tabel list banjar + kartu mobile + pagination
|
||||||
|
- `create/page.tsx` — form tambah banjar
|
||||||
|
- `[id]/page.tsx` — detail + hapus + edit
|
||||||
|
|
||||||
|
### 7. Admin UI Penderita Penyakit (update)
|
||||||
|
- `page.tsx` (list) — tambah kolom Banjar di tabel desktop, field Banjar di kartu mobile, filter Select by banjar
|
||||||
|
- `create/page.tsx` — tambah `Select` banjar (opsional), load `banjarList` on mount
|
||||||
|
- `[id]/page.tsx` (detail) — tambah baris Banjar
|
||||||
|
- `[id]/edit/page.tsx` — tambah `banjarId` ke `formData`, load `banjarList`, tambah `Select` banjar
|
||||||
|
|
||||||
|
### 8. Sidebar Admin — `list_PageAdmin.tsx`
|
||||||
|
- Tambah menu "Banjar" di bawah domain Desa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keputusan Desain
|
||||||
|
- `banjarId` dibuat **opsional** agar data lama tidak terpengaruh
|
||||||
|
- Filter banjar di list page menggunakan Select + clear, tidak memerlukan halaman terpisah
|
||||||
|
- `banjarList` di-load sekali on mount, bukan di-refetch tiap render
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifikasi
|
||||||
|
- `bun run build` ✅ sukses tanpa error TypeScript
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "GrafikKepuasan" ADD COLUMN "banjarId" TEXT;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "GrafikKepuasan" ADD CONSTRAINT "GrafikKepuasan_banjarId_fkey" FOREIGN KEY ("banjarId") REFERENCES "Banjar"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -1050,6 +1050,8 @@ model GrafikKepuasan {
|
|||||||
jenisKelamin String
|
jenisKelamin String
|
||||||
alamat String
|
alamat String
|
||||||
penyakit String
|
penyakit String
|
||||||
|
banjar Banjar? @relation(fields: [banjarId], references: [id])
|
||||||
|
banjarId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
@@ -1152,12 +1154,13 @@ model DoctorSign {
|
|||||||
|
|
||||||
// ========================================= BANJAR ========================================= //
|
// ========================================= BANJAR ========================================= //
|
||||||
model Banjar {
|
model Banjar {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
posyandus Posyandu[]
|
posyandus Posyandu[]
|
||||||
|
grafikKepuasans GrafikKepuasan[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= POSYANDU ========================================= //
|
// ========================================= POSYANDU ========================================= //
|
||||||
|
|||||||
160
src/app/admin/(dashboard)/_state/desa/banjar.ts
Normal file
160
src/app/admin/(dashboard)/_state/desa/banjar.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateBanjar = z.object({
|
||||||
|
name: z.string().min(1, "Nama banjar wajib diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultBanjar = { name: "" };
|
||||||
|
|
||||||
|
const banjar = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultBanjar },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateBanjar.safeParse(banjar.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
banjar.create.loading = true;
|
||||||
|
const res = await fetch("/api/desa/banjar/create", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(banjar.create.form),
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
banjar.findMany.load();
|
||||||
|
return toast.success("Banjar berhasil dibuat");
|
||||||
|
}
|
||||||
|
return toast.error(result?.message || "Gagal membuat banjar");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return toast.error("Gagal membuat banjar");
|
||||||
|
} finally {
|
||||||
|
banjar.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.BanjarGetPayload<object>[],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
banjar.findMany.loading = true;
|
||||||
|
banjar.findMany.page = page;
|
||||||
|
banjar.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({ page: String(page), limit: String(limit) });
|
||||||
|
if (search) params.set("search", search);
|
||||||
|
|
||||||
|
const res = await fetch(`/api/desa/banjar/findMany?${params.toString()}`);
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
banjar.findMany.data = result.data ?? [];
|
||||||
|
banjar.findMany.totalPages = result.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
banjar.findMany.data = [];
|
||||||
|
banjar.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch banjar:", err);
|
||||||
|
banjar.findMany.data = [];
|
||||||
|
banjar.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
banjar.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async delete(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
banjar.delete.loading = true;
|
||||||
|
const res = await fetch(`/api/desa/banjar/del/${id}`, { method: "DELETE" });
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Banjar berhasil dihapus");
|
||||||
|
await banjar.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus banjar");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete banjar:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus banjar");
|
||||||
|
} finally {
|
||||||
|
banjar.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultBanjar },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) { toast.warn("ID tidak valid"); return null; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/banjar/${id}`);
|
||||||
|
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
if (result?.success) {
|
||||||
|
banjar.update.id = result.data.id;
|
||||||
|
banjar.update.form = { name: result.data.name };
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading banjar:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateBanjar.safeParse(banjar.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
banjar.update.loading = true;
|
||||||
|
const res = await fetch(`/api/desa/banjar/${banjar.update.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(banjar.update.form),
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
return toast.success("Banjar berhasil diperbarui");
|
||||||
|
}
|
||||||
|
return toast.error(result?.message || "Gagal mengupdate banjar");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating banjar:", error);
|
||||||
|
toast.error("Gagal mengupdate banjar");
|
||||||
|
} finally {
|
||||||
|
banjar.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default banjar;
|
||||||
@@ -11,6 +11,7 @@ const templateGrafikKepuasan = z.object({
|
|||||||
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||||
alamat: z.string().min(1, "Alamat harus diisi"),
|
alamat: z.string().min(1, "Alamat harus diisi"),
|
||||||
penyakit: z.string().min(1, "Penyakit harus diisi"),
|
penyakit: z.string().min(1, "Penyakit harus diisi"),
|
||||||
|
banjarId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -19,6 +20,7 @@ const defaultForm = {
|
|||||||
jenisKelamin: "",
|
jenisKelamin: "",
|
||||||
alamat: "",
|
alamat: "",
|
||||||
penyakit: "",
|
penyakit: "",
|
||||||
|
banjarId: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const grafikkepuasan = proxy({
|
const grafikkepuasan = proxy({
|
||||||
@@ -62,23 +64,24 @@ const grafikkepuasan = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.GrafikKepuasanGetPayload<{
|
| Prisma.GrafikKepuasanGetPayload<{
|
||||||
omit: {
|
include: { banjar: { select: { id: true; name: true } } };
|
||||||
isActive: true;
|
|
||||||
};
|
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
search: "",
|
search: "",
|
||||||
load: async (page = 1, limit = 10, search = "") => {
|
banjarId: "",
|
||||||
grafikkepuasan.findMany.loading = true; // ✅ Akses langsung via nama path
|
load: async (page = 1, limit = 10, search = "", banjarId = "") => {
|
||||||
|
grafikkepuasan.findMany.loading = true;
|
||||||
grafikkepuasan.findMany.page = page;
|
grafikkepuasan.findMany.page = page;
|
||||||
grafikkepuasan.findMany.search = search;
|
grafikkepuasan.findMany.search = search;
|
||||||
|
grafikkepuasan.findMany.banjarId = banjarId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const query: any = { page, limit };
|
const query: any = { page, limit };
|
||||||
if (search) query.search = search;
|
if (search) query.search = search;
|
||||||
|
if (banjarId) query.banjarId = banjarId;
|
||||||
|
|
||||||
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
|
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
|
||||||
"find-many"
|
"find-many"
|
||||||
@@ -153,6 +156,7 @@ const grafikkepuasan = proxy({
|
|||||||
jenisKelamin: data.jenisKelamin,
|
jenisKelamin: data.jenisKelamin,
|
||||||
alamat: data.alamat,
|
alamat: data.alamat,
|
||||||
penyakit: data.penyakit,
|
penyakit: data.penyakit,
|
||||||
|
banjarId: data.banjarId || "",
|
||||||
};
|
};
|
||||||
return data; // Return the loaded data
|
return data; // Return the loaded data
|
||||||
} else {
|
} else {
|
||||||
@@ -179,6 +183,7 @@ const grafikkepuasan = proxy({
|
|||||||
jenisKelamin: this.form.jenisKelamin,
|
jenisKelamin: this.form.jenisKelamin,
|
||||||
alamat: this.form.alamat,
|
alamat: this.form.alamat,
|
||||||
penyakit: this.form.penyakit,
|
penyakit: this.form.penyakit,
|
||||||
|
banjarId: this.form.banjarId || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cek = templateGrafikKepuasan.safeParse(formData);
|
const cek = templateGrafikKepuasan.safeParse(formData);
|
||||||
@@ -253,6 +258,24 @@ const grafikkepuasan = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
banjarList: {
|
||||||
|
data: [] as { id: string; name: string }[],
|
||||||
|
loading: false,
|
||||||
|
async load() {
|
||||||
|
try {
|
||||||
|
grafikkepuasan.banjarList.loading = true;
|
||||||
|
const res = await fetch("/api/desa/banjar/findMany?limit=100");
|
||||||
|
const result = await res.json();
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
grafikkepuasan.banjarList.data = result.data ?? [];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch banjar list:", err);
|
||||||
|
} finally {
|
||||||
|
grafikkepuasan.banjarList.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default grafikkepuasan;
|
export default grafikkepuasan;
|
||||||
|
|||||||
123
src/app/admin/(dashboard)/desa/banjar/[id]/page.tsx
Normal file
123
src/app/admin/(dashboard)/desa/banjar/[id]/page.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client';
|
||||||
|
import stateBanjar from '@/app/admin/(dashboard)/_state/desa/banjar';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
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 EditBanjar() {
|
||||||
|
const state = useProxy(stateBanjar);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [originalName, setOriginalName] = useState('');
|
||||||
|
const [formData, setFormData] = useState({ name: '' });
|
||||||
|
|
||||||
|
const isFormValid = () => formData.name?.trim() !== '';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
state.update.load(id).then((data) => {
|
||||||
|
if (data) {
|
||||||
|
setFormData({ name: data.name });
|
||||||
|
setOriginalName(data.name);
|
||||||
|
} else {
|
||||||
|
toast.error('Gagal memuat data banjar');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleResetForm = () => {
|
||||||
|
setFormData({ name: originalName });
|
||||||
|
toast.info('Form dikembalikan ke data awal');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formData.name?.trim()) {
|
||||||
|
toast.error('Nama banjar wajib diisi');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
state.update.form = { name: formData.name };
|
||||||
|
await state.update.update();
|
||||||
|
router.push('/admin/desa/banjar');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating banjar:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui banjar');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
|
<Group mb="md">
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
<Title order={4} ml="sm" c="dark">Edit Banjar</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
name="name"
|
||||||
|
label="Nama Banjar"
|
||||||
|
placeholder="Masukkan nama banjar"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button variant="outline" color="gray" radius="md" size="md" onClick={handleResetForm}>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
disabled={!isFormValid() || isSubmitting}
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
!isFormValid() || isSubmitting
|
||||||
|
? 'linear-gradient(135deg, #cccccc, #eeeeee)'
|
||||||
|
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditBanjar;
|
||||||
103
src/app/admin/(dashboard)/desa/banjar/create/page.tsx
Normal file
103
src/app/admin/(dashboard)/desa/banjar/create/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
import stateBanjar from '@/app/admin/(dashboard)/_state/desa/banjar';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function CreateBanjar() {
|
||||||
|
const state = useProxy(stateBanjar);
|
||||||
|
const router = useRouter();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const isFormValid = () => state.create.form.name?.trim() !== '';
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
state.create.form = { name: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!state.create.form.name?.trim()) {
|
||||||
|
toast.error('Nama banjar wajib diisi');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
await state.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push('/admin/desa/banjar');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating banjar:', error);
|
||||||
|
toast.error('Gagal menambahkan banjar');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
|
<Group mb="md">
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
<Title order={4} ml="sm" c="dark">Tambah Banjar</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Banjar"
|
||||||
|
placeholder="Masukkan nama banjar"
|
||||||
|
value={state.create.form.name || ''}
|
||||||
|
onChange={(e) => (state.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button variant="outline" color="gray" radius="md" size="md" onClick={resetForm}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
disabled={!isFormValid() || isSubmitting}
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
!isFormValid() || isSubmitting
|
||||||
|
? 'linear-gradient(135deg, #cccccc, #eeeeee)'
|
||||||
|
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateBanjar;
|
||||||
211
src/app/admin/(dashboard)/desa/banjar/page.tsx
Normal file
211
src/app/admin/(dashboard)/desa/banjar/page.tsx
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconPlus, 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 { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
|
import stateBanjar from '../../_state/desa/banjar';
|
||||||
|
|
||||||
|
function Banjar() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title="Data Banjar"
|
||||||
|
placeholder="Cari nama banjar..."
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListBanjar search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListBanjar({ search }: { search: string }) {
|
||||||
|
const state = useProxy(stateBanjar);
|
||||||
|
const router = useRouter();
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
|
const { data, loading, load, page, totalPages } = state.findMany;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10, debouncedSearch);
|
||||||
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
state.delete.delete(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={600} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={{ base: 'sm', md: 'lg' }}>
|
||||||
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
|
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
|
||||||
|
<Title order={4} lh={1.2}>Daftar Banjar</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/desa/banjar/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover layout="fixed" withColumnBorders={false} miw={0}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="60%">
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Banjar</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh w="20%">
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">Edit</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh w="20%">
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">Hapus</Text>
|
||||||
|
</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" fw={500} lh={1.45} truncate="end">{item.name}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="compact-sm"
|
||||||
|
onClick={() => router.push(`/admin/desa/banjar/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="compact-sm"
|
||||||
|
disabled={state.delete.loading}
|
||||||
|
onClick={() => { setSelectedId(item.id); setModalHapus(true); }}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={3}>
|
||||||
|
<Center py={24}>
|
||||||
|
<Text c="dimmed" fz="sm">Tidak ada data banjar</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Stack hiddenFrom="md" gap="xs" mt="md">
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder radius="md" p="sm" bg="white">
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Banjar</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.45}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Group mt="sm" justify="flex-end" gap="xs">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="compact-xs"
|
||||||
|
onClick={() => router.push(`/admin/desa/banjar/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconEdit size={14} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="compact-xs"
|
||||||
|
disabled={state.delete.loading}
|
||||||
|
onClick={() => { setSelectedId(item.id); setModalHapus(true); }}
|
||||||
|
>
|
||||||
|
<IconTrash size={14} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={32}>
|
||||||
|
<Text c="dimmed" fz="sm">Tidak ada data banjar</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Center mt={{ base: 'lg', md: 'xl' }}>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text="Apakah anda yakin ingin menghapus banjar ini? Banjar yang masih digunakan posyandu tidak bisa dihapus."
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Banjar;
|
||||||
@@ -9,10 +9,12 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Paper,
|
Paper,
|
||||||
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -43,6 +45,7 @@ function EditGrafikHasilKepuasan() {
|
|||||||
jenisKelamin: '',
|
jenisKelamin: '',
|
||||||
alamat: '',
|
alamat: '',
|
||||||
penyakit: '',
|
penyakit: '',
|
||||||
|
banjarId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [originalData, setOriginalData] = useState({
|
const [originalData, setOriginalData] = useState({
|
||||||
@@ -51,8 +54,13 @@ function EditGrafikHasilKepuasan() {
|
|||||||
jenisKelamin: '',
|
jenisKelamin: '',
|
||||||
alamat: '',
|
alamat: '',
|
||||||
penyakit: '',
|
penyakit: '',
|
||||||
|
banjarId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
editState.banjarList.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load data once
|
// Load data once
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -70,6 +78,7 @@ function EditGrafikHasilKepuasan() {
|
|||||||
jenisKelamin: data.jenisKelamin || '',
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
alamat: data.alamat || '',
|
alamat: data.alamat || '',
|
||||||
penyakit: data.penyakit || '',
|
penyakit: data.penyakit || '',
|
||||||
|
banjarId: data.banjarId || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
setOriginalData({
|
setOriginalData({
|
||||||
@@ -78,6 +87,7 @@ function EditGrafikHasilKepuasan() {
|
|||||||
jenisKelamin: data.jenisKelamin || '',
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
alamat: data.alamat || '',
|
alamat: data.alamat || '',
|
||||||
penyakit: data.penyakit || '',
|
penyakit: data.penyakit || '',
|
||||||
|
banjarId: data.banjarId || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -95,13 +105,7 @@ function EditGrafikHasilKepuasan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleResetForm = () => {
|
const handleResetForm = () => {
|
||||||
setFormData({
|
setFormData({ ...originalData });
|
||||||
nama: originalData.nama,
|
|
||||||
tanggal: originalData.tanggal,
|
|
||||||
jenisKelamin: originalData.jenisKelamin,
|
|
||||||
alamat: originalData.alamat,
|
|
||||||
penyakit: originalData.penyakit,
|
|
||||||
});
|
|
||||||
toast.info("Form dikembalikan ke data awal");
|
toast.info("Form dikembalikan ke data awal");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,6 +187,15 @@ function EditGrafikHasilKepuasan() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<Select
|
||||||
|
label="Banjar"
|
||||||
|
placeholder="Pilih banjar (opsional)"
|
||||||
|
data={editState.banjarList.data.map((b) => ({ value: b.id, label: b.name }))}
|
||||||
|
value={formData.banjarId || null}
|
||||||
|
onChange={(val) => handleChange('banjarId', val ?? '')}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ function DetailGrafikHasilKepuasan() {
|
|||||||
<Text fz="md" c="dimmed">{data.penyakit || '-'}</Text>
|
<Text fz="md" c="dimmed">{data.penyakit || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Banjar</Text>
|
||||||
|
<Text fz="md" c="dimmed">{(data as any).banjar?.name || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Aksi */}
|
{/* Aksi */}
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Paper,
|
Paper,
|
||||||
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -25,6 +27,10 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateGrafikKepuasan.banjarList.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Check if form is valid
|
// Check if form is valid
|
||||||
const isFormValid = () => {
|
const isFormValid = () => {
|
||||||
return (
|
return (
|
||||||
@@ -43,6 +49,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
jenisKelamin: "",
|
jenisKelamin: "",
|
||||||
alamat: "",
|
alamat: "",
|
||||||
penyakit: "",
|
penyakit: "",
|
||||||
|
banjarId: "",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,6 +155,15 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
onChange={(e) => (stateGrafikKepuasan.create.form.penyakit = e.target.value)}
|
onChange={(e) => (stateGrafikKepuasan.create.form.penyakit = e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label="Banjar"
|
||||||
|
placeholder="Pilih banjar (opsional)"
|
||||||
|
data={stateGrafikKepuasan.banjarList.data.map((b) => ({ value: b.id, label: b.name }))}
|
||||||
|
value={stateGrafikKepuasan.create.form.banjarId || null}
|
||||||
|
onChange={(val) => (stateGrafikKepuasan.create.form.banjarId = val ?? '')}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
|
Select,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
@@ -62,21 +63,28 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
jenisKelamin: string;
|
jenisKelamin: string;
|
||||||
alamat: string;
|
alamat: string;
|
||||||
penyakit: string;
|
penyakit: string;
|
||||||
|
banjar?: { id: string; name: string } | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [selectedBanjar, setSelectedBanjar] = useState<string | null>(null);
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = stateGrafikKepuasan.findMany;
|
const { data, page, totalPages, loading, load } = stateGrafikKepuasan.findMany;
|
||||||
|
const { data: banjarList } = stateGrafikKepuasan.banjarList;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateGrafikKepuasan.banjarList.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
load(page, 10, debouncedSearch);
|
load(page, 10, debouncedSearch, selectedBanjar ?? '');
|
||||||
}, [page, debouncedSearch]);
|
}, [page, debouncedSearch, selectedBanjar]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -113,6 +121,11 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
|
const banjarOptions = [
|
||||||
|
{ value: '', label: 'Semua Banjar' },
|
||||||
|
...banjarList.map((b) => ({ value: b.id, label: b.name })),
|
||||||
|
];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={{ base: 'md', md: 'lg' }}>
|
<Stack py={{ base: 'md', md: 'lg' }}>
|
||||||
@@ -146,16 +159,32 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
{/* Filter Banjar */}
|
||||||
|
<Box mb="sm" maw={280}>
|
||||||
|
<Select
|
||||||
|
placeholder="Filter berdasarkan banjar"
|
||||||
|
data={banjarOptions}
|
||||||
|
value={selectedBanjar ?? ''}
|
||||||
|
onChange={(val) => {
|
||||||
|
setSelectedBanjar(val || null);
|
||||||
|
load(1, 10, debouncedSearch, val ?? '');
|
||||||
|
}}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Desktop Table */}
|
{/* Desktop Table */}
|
||||||
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover
|
<Table highlightOnHover
|
||||||
layout="fixed" // 🔥 PENTING
|
layout="fixed"
|
||||||
withColumnBorders={false}>
|
withColumnBorders={false}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Tanggal</TableTh>
|
<TableTh>Tanggal</TableTh>
|
||||||
<TableTh>Jenis Kelamin</TableTh>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
|
<TableTh>Banjar</TableTh>
|
||||||
<TableTh>Penyakit</TableTh>
|
<TableTh>Penyakit</TableTh>
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -177,6 +206,9 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
<TableTd fz="md" fw={500} lh={1.5}>
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
{item.jenisKelamin}
|
{item.jenisKelamin}
|
||||||
</TableTd>
|
</TableTd>
|
||||||
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.banjar?.name ?? '-'}
|
||||||
|
</TableTd>
|
||||||
<TableTd fz="md" fw={500} lh={1.5}>
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
{item.penyakit}
|
{item.penyakit}
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -200,7 +232,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTd colSpan={6}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text c="dimmed" fz="sm" lh={1.4}>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
Tidak ada data kepuasan masyarakat yang cocok
|
Tidak ada data kepuasan masyarakat yang cocok
|
||||||
@@ -244,6 +276,13 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
{item.jenisKelamin}
|
{item.jenisKelamin}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Banjar
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.banjar?.name ?? '-'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Text fz="sm" fw={600} lh={1.4}>
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
Penyakit
|
Penyakit
|
||||||
</Text>
|
</Text>
|
||||||
@@ -285,7 +324,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10, debouncedSearch, selectedBanjar ?? '');
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
@@ -339,4 +378,4 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GrafikHasilKepuasanMasyarakat;
|
export default GrafikHasilKepuasanMasyarakat;
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ export const devBar = [
|
|||||||
id: "Desa_9",
|
id: "Desa_9",
|
||||||
name: "Event Budaya",
|
name: "Event Budaya",
|
||||||
path: "/admin/desa/event-budaya"
|
path: "/admin/desa/event-budaya"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_10",
|
||||||
|
name: "Banjar",
|
||||||
|
path: "/admin/desa/banjar"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -574,6 +579,11 @@ export const navBar = [
|
|||||||
id: "Desa_9",
|
id: "Desa_9",
|
||||||
name: "Event Budaya",
|
name: "Event Budaya",
|
||||||
path: "/admin/desa/event-budaya"
|
path: "/admin/desa/event-budaya"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_10",
|
||||||
|
name: "Banjar",
|
||||||
|
path: "/admin/desa/banjar"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -1035,6 +1045,11 @@ export const role1 = [
|
|||||||
id: "Desa_9",
|
id: "Desa_9",
|
||||||
name: "Event Budaya",
|
name: "Event Budaya",
|
||||||
path: "/admin/desa/event-budaya"
|
path: "/admin/desa/event-budaya"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_10",
|
||||||
|
name: "Banjar",
|
||||||
|
path: "/admin/desa/banjar"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
24
src/app/api/[[...slugs]]/_lib/desa/banjar/create.ts
Normal file
24
src/app/api/[[...slugs]]/_lib/desa/banjar/create.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormCreate = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function banjarCreate(context: Context) {
|
||||||
|
const body = (await context.body) as FormCreate;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await prisma.banjar.create({
|
||||||
|
data: { name: body.name },
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil membuat banjar",
|
||||||
|
data: result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating banjar:", error);
|
||||||
|
throw new Error("Gagal membuat banjar: " + (error as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/app/api/[[...slugs]]/_lib/desa/banjar/del.ts
Normal file
42
src/app/api/[[...slugs]]/_lib/desa/banjar/del.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function banjarDelete(context: Context) {
|
||||||
|
try {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Response.json({ success: false, message: "ID tidak boleh kosong" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const posyanduCount = await prisma.posyandu.count({
|
||||||
|
where: { banjarId: id, isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (posyanduCount > 0) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: `Banjar tidak dapat dihapus karena masih digunakan oleh ${posyanduCount} posyandu`,
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.banjar.update({
|
||||||
|
where: { id },
|
||||||
|
data: { isActive: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: "Banjar berhasil dihapus" };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Delete banjar error:", error);
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "Gagal menghapus banjar: " + (error instanceof Error ? error.message : "Unknown error"),
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/app/api/[[...slugs]]/_lib/desa/banjar/findMany.ts
Normal file
43
src/app/api/[[...slugs]]/_lib/desa/banjar/findMany.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function banjarFindMany(context: Context) {
|
||||||
|
const page = Number(context.query.page) || 1;
|
||||||
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || "";
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
where.OR = [{ name: { contains: search, mode: "insensitive" } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.banjar.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
}),
|
||||||
|
prisma.banjar.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil data banjar",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di banjar findMany:", e);
|
||||||
|
return { success: false, message: "Gagal mengambil data banjar" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default banjarFindMany;
|
||||||
21
src/app/api/[[...slugs]]/_lib/desa/banjar/findUnique.ts
Normal file
21
src/app/api/[[...slugs]]/_lib/desa/banjar/findUnique.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function banjarFindUnique(context: Context) {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Response.json({ success: false, message: "ID wajib diisi" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.banjar.findUnique({ where: { id } });
|
||||||
|
if (!data) {
|
||||||
|
return Response.json({ success: false, message: "Banjar tidak ditemukan" }, { status: 404 });
|
||||||
|
}
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching banjar:", error);
|
||||||
|
throw new Error("Gagal mengambil data banjar");
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/app/api/[[...slugs]]/_lib/desa/banjar/index.ts
Normal file
22
src/app/api/[[...slugs]]/_lib/desa/banjar/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import banjarCreate from "./create";
|
||||||
|
import banjarDelete from "./del";
|
||||||
|
import banjarFindMany from "./findMany";
|
||||||
|
import banjarFindUnique from "./findUnique";
|
||||||
|
import banjarUpdate from "./updt";
|
||||||
|
|
||||||
|
const Banjar = new Elysia({
|
||||||
|
prefix: "/banjar",
|
||||||
|
tags: ["Desa / Banjar"],
|
||||||
|
})
|
||||||
|
.post("/create", banjarCreate, {
|
||||||
|
body: t.Object({ name: t.String() }),
|
||||||
|
})
|
||||||
|
.get("/findMany", banjarFindMany)
|
||||||
|
.get("/:id", banjarFindUnique)
|
||||||
|
.put("/:id", banjarUpdate, {
|
||||||
|
body: t.Object({ name: t.String() }),
|
||||||
|
})
|
||||||
|
.delete("/del/:id", banjarDelete);
|
||||||
|
|
||||||
|
export default Banjar;
|
||||||
26
src/app/api/[[...slugs]]/_lib/desa/banjar/updt.ts
Normal file
26
src/app/api/[[...slugs]]/_lib/desa/banjar/updt.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormUpdate = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function banjarUpdate(context: Context) {
|
||||||
|
const body = (await context.body) as FormUpdate;
|
||||||
|
const id = context.params.id as string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await prisma.banjar.update({
|
||||||
|
where: { id },
|
||||||
|
data: { name: body.name },
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mengupdate banjar",
|
||||||
|
data: result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating banjar:", error);
|
||||||
|
throw new Error("Gagal mengupdate banjar: " + (error as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import Musik from "./musik";
|
|||||||
import KegiatanDesa from "./kegiatan-desa";
|
import KegiatanDesa from "./kegiatan-desa";
|
||||||
import KategoriKegiatan from "./kegiatan-desa/kategori-kegiatan";
|
import KategoriKegiatan from "./kegiatan-desa/kategori-kegiatan";
|
||||||
import EventBudaya from "./event-budaya";
|
import EventBudaya from "./event-budaya";
|
||||||
|
import Banjar from "./banjar";
|
||||||
|
|
||||||
|
|
||||||
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
||||||
@@ -36,6 +37,7 @@ const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
|||||||
.use(KegiatanDesa)
|
.use(KegiatanDesa)
|
||||||
.use(KategoriKegiatan)
|
.use(KategoriKegiatan)
|
||||||
.use(EventBudaya)
|
.use(EventBudaya)
|
||||||
|
.use(Banjar)
|
||||||
|
|
||||||
|
|
||||||
export default Desa;
|
export default Desa;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type FormCreate = {
|
|||||||
jenisKelamin: string;
|
jenisKelamin: string;
|
||||||
alamat: string;
|
alamat: string;
|
||||||
penyakit: string;
|
penyakit: string;
|
||||||
|
banjarId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function grafikKepuasanCreate(context: Context) {
|
export default async function grafikKepuasanCreate(context: Context) {
|
||||||
@@ -19,14 +20,9 @@ export default async function grafikKepuasanCreate(context: Context) {
|
|||||||
jenisKelamin: body.jenisKelamin,
|
jenisKelamin: body.jenisKelamin,
|
||||||
alamat: body.alamat,
|
alamat: body.alamat,
|
||||||
penyakit: body.penyakit,
|
penyakit: body.penyakit,
|
||||||
|
...(body.banjarId ? { banjarId: body.banjarId } : {}),
|
||||||
},
|
},
|
||||||
select: {
|
include: { banjar: { select: { id: true, name: true } } },
|
||||||
nama: true,
|
|
||||||
tanggal: true,
|
|
||||||
jenisKelamin: true,
|
|
||||||
alamat: true,
|
|
||||||
penyakit: true,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ async function grafikKepuasanFindMany(context: Context) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter berdasarkan banjarId jika ada
|
||||||
|
const banjarId = (context.query.banjarId as string) || '';
|
||||||
|
if (banjarId) {
|
||||||
|
where.banjarId = banjarId;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ambil data dan total count secara paralel
|
// Ambil data dan total count secara paralel
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
@@ -31,6 +37,7 @@ async function grafikKepuasanFindMany(context: Context) {
|
|||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { nama: 'asc' },
|
orderBy: { nama: 'asc' },
|
||||||
|
include: { banjar: { select: { id: true, name: true } } },
|
||||||
}),
|
}),
|
||||||
prisma.grafikKepuasan.count({ where }),
|
prisma.grafikKepuasan.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default async function grafikKepuasanFindUnique(request: Request) {
|
|||||||
|
|
||||||
const data = await prisma.grafikKepuasan.findUnique({
|
const data = await prisma.grafikKepuasan.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
include: { banjar: { select: { id: true, name: true } } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const GrafikKepuasan = new Elysia({
|
|||||||
jenisKelamin: t.String(),
|
jenisKelamin: t.String(),
|
||||||
alamat: t.String(),
|
alamat: t.String(),
|
||||||
penyakit: t.String(),
|
penyakit: t.String(),
|
||||||
|
banjarId: t.Optional(t.String()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.put("/:id", grafikKepuasanUpdate, {
|
.put("/:id", grafikKepuasanUpdate, {
|
||||||
@@ -30,6 +31,7 @@ const GrafikKepuasan = new Elysia({
|
|||||||
jenisKelamin: t.String(),
|
jenisKelamin: t.String(),
|
||||||
alamat: t.String(),
|
alamat: t.String(),
|
||||||
penyakit: t.String(),
|
penyakit: t.String(),
|
||||||
|
banjarId: t.Optional(t.String()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.delete("/del/:id", grafikKepuasanDelete)
|
.delete("/del/:id", grafikKepuasanDelete)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type FormUpdate = {
|
|||||||
jenisKelamin: string;
|
jenisKelamin: string;
|
||||||
alamat: string;
|
alamat: string;
|
||||||
penyakit: string;
|
penyakit: string;
|
||||||
|
banjarId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function grafikKepuasanUpdate(context: Context) {
|
export default async function grafikKepuasanUpdate(context: Context) {
|
||||||
@@ -22,7 +23,9 @@ export default async function grafikKepuasanUpdate(context: Context) {
|
|||||||
jenisKelamin: body.jenisKelamin,
|
jenisKelamin: body.jenisKelamin,
|
||||||
alamat: body.alamat,
|
alamat: body.alamat,
|
||||||
penyakit: body.penyakit,
|
penyakit: body.penyakit,
|
||||||
|
banjarId: body.banjarId || null,
|
||||||
},
|
},
|
||||||
|
include: { banjar: { select: { id: true, name: true } } },
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user