API & UI Menu Inovasi & Submenu Layan Online Desa 2 tabs
This commit is contained in:
@@ -50,7 +50,7 @@
|
|||||||
"embla-carousel-autoplay": "^8.5.2",
|
"embla-carousel-autoplay": "^8.5.2",
|
||||||
"embla-carousel-react": "^7.1.0",
|
"embla-carousel-react": "^7.1.0",
|
||||||
"form-data": "^4.0.2",
|
"form-data": "^4.0.2",
|
||||||
"framer-motion": "^12.4.1",
|
"framer-motion": "^12.23.5",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"jotai": "^2.12.3",
|
"jotai": "^2.12.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
245
prisma/migrations/20250715072239_nico_15_jul_25_1/migration.sql
Normal file
245
prisma/migrations/20250715072239_nico_15_jul_25_1/migration.sql
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `belanjaId` on the `ApbDesa` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `pembiayaanId` on the `ApbDesa` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `pendapatanId` on the `ApbDesa` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `KegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `KlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `RincianBelanja` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `_ApbDesaToKegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `_BelanjaToKlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `_KlasifikasiBelanjaToRincianBelanja` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Changed the type of `tanggal` on the `DaftarInformasiPublik` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||||
|
- Added the required column `updatedAt` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_belanjaId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pembiayaanId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pendapatanId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApbDesa" DROP COLUMN "belanjaId",
|
||||||
|
DROP COLUMN "pembiayaanId",
|
||||||
|
DROP COLUMN "pendapatanId",
|
||||||
|
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "tanggal",
|
||||||
|
ADD COLUMN "tanggal" DATE NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Pembiayaan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "KegiatanSubak";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "KlasifikasiBelanja";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "RincianBelanja";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_ApbDesaToKegiatanSubak";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_BelanjaToKlasifikasiBelanja";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_KlasifikasiBelanjaToRincianBelanja";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DesaDigital" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"imageId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DesaDigital_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProgramKreatif" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"slug" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"icon" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "ProgramKreatif_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "KolaborasiInovasi" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"tahun" INTEGER NOT NULL,
|
||||||
|
"slug" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"kolaborator" TEXT NOT NULL,
|
||||||
|
"imageId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "KolaborasiInovasi_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "InfoTekno" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"imageId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "InfoTekno_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AjukanIdeInovatif" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"alamat" TEXT NOT NULL,
|
||||||
|
"namaIde" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"masalah" TEXT NOT NULL,
|
||||||
|
"benefit" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "AjukanIdeInovatif_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AdministrasiOnline" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"alamat" TEXT NOT NULL,
|
||||||
|
"nomorTelepon" TEXT NOT NULL,
|
||||||
|
"jenisLayananId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "AdministrasiOnline_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "JenisLayanan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"nama" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "JenisLayanan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_ApbDesaPembiayaan" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_ApbDesaPembiayaan_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_ApbDesaBelanja" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_ApbDesaBelanja_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_ApbDesaPendapatan" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_ApbDesaPendapatan_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_ApbDesaPembiayaan_B_index" ON "_ApbDesaPembiayaan"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_ApbDesaBelanja_B_index" ON "_ApbDesaBelanja"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_ApbDesaPendapatan_B_index" ON "_ApbDesaPendapatan"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DesaDigital" ADD CONSTRAINT "DesaDigital_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "KolaborasiInovasi" ADD CONSTRAINT "KolaborasiInovasi_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "InfoTekno" ADD CONSTRAINT "InfoTekno_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "AdministrasiOnline" ADD CONSTRAINT "AdministrasiOnline_jenisLayananId_fkey" FOREIGN KEY ("jenisLayananId") REFERENCES "JenisLayanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pembiayaan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pendapatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -1387,3 +1387,43 @@ model InfoTekno {
|
|||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= AJUKAN IDE INOVATIF ========================================= //
|
||||||
|
model AjukanIdeInovatif {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
alamat String
|
||||||
|
namaIde String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
masalah String @db.Text
|
||||||
|
benefit String @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================= LAYANAN ONLINE DESA ========================================= //
|
||||||
|
model AdministrasiOnline {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
alamat String
|
||||||
|
nomorTelepon String
|
||||||
|
jenisLayanan JenisLayanan @relation(fields: [jenisLayananId], references: [id])
|
||||||
|
jenisLayananId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model JenisLayanan {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
nama String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
AdministrasiOnline AdministrasiOnline[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -275,8 +275,7 @@ const kategoriProduk = proxy({
|
|||||||
data: null as Array<{
|
data: null as Array<{
|
||||||
id: string;
|
id: string;
|
||||||
nama: string;
|
nama: string;
|
||||||
}>
|
}> | null,
|
||||||
| null,
|
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
|
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
@@ -408,29 +407,39 @@ const kategoriProduk = proxy({
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('Update failed with status:', response.status, 'Response:', result);
|
console.error(
|
||||||
|
"Update failed with status:",
|
||||||
|
response.status,
|
||||||
|
"Response:",
|
||||||
|
result
|
||||||
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
result?.message || `Gagal mengupdate kategori produk (${response.status})`
|
result?.message ||
|
||||||
|
`Gagal mengupdate kategori produk (${response.status})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success(result.message || "Berhasil memperbarui kategori produk");
|
toast.success(
|
||||||
|
result.message || "Berhasil memperbarui kategori produk"
|
||||||
|
);
|
||||||
await kategoriProduk.findMany.load(); // refresh list
|
await kategoriProduk.findMany.load(); // refresh list
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "Gagal mengupdate kategori produk");
|
throw new Error(
|
||||||
|
result.message || "Gagal mengupdate kategori produk"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If JSON parsing fails, try to get the response text for better error messages
|
// If JSON parsing fails, try to get the response text for better error messages
|
||||||
try {
|
try {
|
||||||
const text = await responseClone.text();
|
const text = await responseClone.text();
|
||||||
console.error('Error response text:', text);
|
console.error("Error response text:", text);
|
||||||
throw new Error(`Gagal memproses respons dari server: ${text}`);
|
throw new Error(`Gagal memproses respons dari server: ${text}`);
|
||||||
} catch (textError) {
|
} catch (textError) {
|
||||||
console.error('Error parsing response as text:', textError);
|
console.error("Error parsing response as text:", textError);
|
||||||
console.error('Original error:', error);
|
console.error("Original error:", error);
|
||||||
throw new Error('Gagal memproses respons dari server');
|
throw new Error("Gagal memproses respons dari server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -454,6 +463,6 @@ const kategoriProduk = proxy({
|
|||||||
|
|
||||||
const pasarDesaState = proxy({
|
const pasarDesaState = proxy({
|
||||||
pasarDesa,
|
pasarDesa,
|
||||||
kategoriProduk
|
kategoriProduk,
|
||||||
});
|
});
|
||||||
export default pasarDesaState;
|
export default pasarDesaState;
|
||||||
|
|||||||
123
src/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif.ts
Normal file
123
src/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
deskripsi: z.string().min(1).max(5000),
|
||||||
|
alamat: z.string().min(1).max(5000),
|
||||||
|
namaIde: z.string().min(1).max(5000),
|
||||||
|
masalah: z.string().min(1).max(5000),
|
||||||
|
benefit: z.string().min(1).max(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
alamat: "",
|
||||||
|
namaIde: "",
|
||||||
|
masalah: "",
|
||||||
|
benefit: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ajukanIdeInovatifState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(ajukanIdeInovatifState.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ajukanIdeInovatifState.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.inovasi.ajukanideinovatif["create"].post(
|
||||||
|
ajukanIdeInovatifState.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
ajukanIdeInovatifState.findMany.load();
|
||||||
|
return toast.success("Ajukan Ide Inovatif berhasil di kirim");
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
ajukanIdeInovatifState.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.AjukanIdeInovatifGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
ajukanIdeInovatifState.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.AjukanIdeInovatifGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/inovasi/ajukanideinovatif/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
ajukanIdeInovatifState.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
ajukanIdeInovatifState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading ajukan ide inovatif:", error);
|
||||||
|
ajukanIdeInovatifState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ajukanIdeInovatifState.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus");
|
||||||
|
await ajukanIdeInovatifState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus ajukan ide inovatif");
|
||||||
|
} finally {
|
||||||
|
ajukanIdeInovatifState.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export default ajukanIdeInovatifState;
|
||||||
403
src/app/admin/(dashboard)/_state/inovasi/layanan-online-desa.ts
Normal file
403
src/app/admin/(dashboard)/_state/inovasi/layanan-online-desa.ts
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// ========================================= ADMINISTRASI ONLINE ========================================= //
|
||||||
|
const templateAdministrasiOnlineForm = z.object({
|
||||||
|
name: z.string().min(1, "Nama minimal 1 karakter"),
|
||||||
|
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
|
||||||
|
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
|
||||||
|
jenisLayananId: z.string().min(1, "Jenis layanan minimal 1 karakter"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultAdministrasiOnlineForm = {
|
||||||
|
name: "",
|
||||||
|
alamat: "",
|
||||||
|
nomorTelepon: "",
|
||||||
|
jenisLayananId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const administrasiOnline = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultAdministrasiOnlineForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateAdministrasiOnlineForm.safeParse(
|
||||||
|
administrasiOnline.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
administrasiOnline.create.loading = true;
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
|
||||||
|
"create"
|
||||||
|
].post(administrasiOnline.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
administrasiOnline.findMany.load();
|
||||||
|
return toast.success("Data berhasil ditambahkan");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menambahkan data");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Gagal menambahkan data");
|
||||||
|
} finally {
|
||||||
|
administrasiOnline.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as Array<
|
||||||
|
Prisma.AdministrasiOnlineGetPayload<{
|
||||||
|
include: {
|
||||||
|
jenisLayanan: true;
|
||||||
|
};
|
||||||
|
}>
|
||||||
|
> | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(page = 1, limit = 10) {
|
||||||
|
administrasiOnline.findMany.loading = true;
|
||||||
|
administrasiOnline.findMany.page = page;
|
||||||
|
try {
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query: {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
administrasiOnline.findMany.data = res.data.data ?? [];
|
||||||
|
administrasiOnline.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch administrasi online paginated:", err);
|
||||||
|
} finally {
|
||||||
|
administrasiOnline.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.AdministrasiOnlineGetPayload<{
|
||||||
|
include: {
|
||||||
|
jenisLayanan: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
administrasiOnline.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch administrasi online:", res.statusText);
|
||||||
|
administrasiOnline.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching administrasi online:", error);
|
||||||
|
administrasiOnline.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
administrasiOnline.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "Administrasi online berhasil dihapus"
|
||||||
|
);
|
||||||
|
await administrasiOnline.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus administrasi online");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus administrasi online");
|
||||||
|
} finally {
|
||||||
|
administrasiOnline.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================= JENIS LAYANAN ========================================= //
|
||||||
|
const templateJenisLayananForm = z.object({
|
||||||
|
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||||
|
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultJenisLayananForm = {
|
||||||
|
nama: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const jenisLayanan = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultJenisLayananForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateJenisLayananForm.safeParse(jenisLayanan.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
jenisLayanan.create.loading = true;
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
||||||
|
"create"
|
||||||
|
].post(jenisLayanan.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
jenisLayanan.findMany.load();
|
||||||
|
return toast.success("Data berhasil ditambahkan");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menambahkan data");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Gagal menambahkan data");
|
||||||
|
} finally {
|
||||||
|
jenisLayanan.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as Array<{
|
||||||
|
id: string;
|
||||||
|
nama: string;
|
||||||
|
deskripsi: string;
|
||||||
|
}> | null,
|
||||||
|
async load() {
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
jenisLayanan.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.JenisLayananGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
jenisLayanan.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
jenisLayanan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
jenisLayanan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
jenisLayanan.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Jenis layanan berhasil dihapus");
|
||||||
|
await jenisLayanan.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus jenis layanan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus jenis layanan");
|
||||||
|
} finally {
|
||||||
|
jenisLayanan.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultJenisLayananForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${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,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading jenis layanan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateJenisLayananForm.safeParse(jenisLayanan.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
jenisLayanan.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${jenisLayanan.edit.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: jenisLayanan.edit.form.nama,
|
||||||
|
deskripsi: jenisLayanan.edit.form.deskripsi,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clone the response to avoid 'body already read' error
|
||||||
|
const responseClone = response.clone();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(
|
||||||
|
"Update failed with status:",
|
||||||
|
response.status,
|
||||||
|
"Response:",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
result?.message ||
|
||||||
|
`Gagal mengupdate jenis layanan (${response.status})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(
|
||||||
|
result.message || "Berhasil memperbarui jenis layanan"
|
||||||
|
);
|
||||||
|
await jenisLayanan.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate jenis layanan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If JSON parsing fails, try to get the response text for better error messages
|
||||||
|
try {
|
||||||
|
const text = await responseClone.text();
|
||||||
|
console.error("Error response text:", text);
|
||||||
|
throw new Error(`Gagal memproses respons dari server: ${text}`);
|
||||||
|
} catch (textError) {
|
||||||
|
console.error("Error parsing response as text:", textError);
|
||||||
|
console.error("Original error:", error);
|
||||||
|
throw new Error("Gagal memproses respons dari server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating jenis layanan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Gagal mengupdate jenis layanan"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
jenisLayanan.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
jenisLayanan.edit.id = "";
|
||||||
|
jenisLayanan.edit.form = { ...defaultJenisLayananForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const layananonlineDesa = proxy({
|
||||||
|
administrasiOnline,
|
||||||
|
jenisLayanan,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default layananonlineDesa;
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import ajukanIdeInovatifState from '../../../_state/inovasi/ajukan-ide-inovatif';
|
||||||
|
|
||||||
|
function DetailAjukanIdeInofativDesa() {
|
||||||
|
const state = useProxy(ajukanIdeInovatifState)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
state.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/inovasi/ajukan-ide-inovatif")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
<Flex justify="space-between" gap={"xs"}>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Ajukan Ide Inovatif Desa</Text>
|
||||||
|
<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>
|
||||||
|
</Flex>
|
||||||
|
{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?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.alamat }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama Ide Inovatif</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.namaIde}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Masalah</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.masalah}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Benefit</Text>
|
||||||
|
<Text fz={"lg"}>{state.findUnique.data?.benefit}</Text>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus ajukan ide inovatif ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailAjukanIdeInofativDesa;
|
||||||
@@ -1,59 +1,91 @@
|
|||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
|
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import React from 'react';
|
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 HeaderSearch from '../../_com/header';
|
||||||
|
import ajukanIdeInovatifState from '../../_state/inovasi/ajukan-ide-inovatif';
|
||||||
|
|
||||||
function AjukanIdeInofativ() {
|
function AjukanIdeInovatif() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Ajukan Ide Inovatif'
|
title='Ajukan Ide Inovatif'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListAjukanIdeInovatif />
|
<ListAjukanIdeInovatif search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListAjukanIdeInovatif() {
|
function ListAjukanIdeInovatif({ search }: { search: string }) {
|
||||||
|
const state = useProxy(ajukanIdeInovatifState)
|
||||||
|
const router = useRouter()
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const filteredData = (state.findMany.data || []).filter(item => {
|
||||||
|
const keyword = search.toLowerCase();
|
||||||
|
return (
|
||||||
|
item.name.toLowerCase().includes(keyword) ||
|
||||||
|
item.deskripsi.toLowerCase().includes(keyword) ||
|
||||||
|
item.alamat.toLowerCase().includes(keyword) ||
|
||||||
|
item.namaIde.toLowerCase().includes(keyword) ||
|
||||||
|
item.masalah.toLowerCase().includes(keyword) ||
|
||||||
|
item.benefit.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Title mb={10} order={3}>List Ajukan Ide Inovatif</Title>
|
||||||
<Title order={4}>List Ajukan Ide Inovatif</Title>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<Box>
|
|
||||||
<Table striped withRowBorders withColumnBorders withTableBorder>
|
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>No</TableTh>
|
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Alamat</TableTh>
|
<TableTh>Alamat</TableTh>
|
||||||
<TableTh>Nama Ide Inovatif</TableTh>
|
<TableTh>Nama Ide Inovatif</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
<TableTh>Masalah yang ingin diatasi</TableTh>
|
|
||||||
<TableTh>Benefit</TableTh>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
<TableTr>
|
<TableTr key={item.id}>
|
||||||
<TableTd>1</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>nama</TableTd>
|
<TableTd>
|
||||||
<TableTd>alamat</TableTd>
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.alamat }} />
|
||||||
<TableTd>ide inovatif</TableTd>
|
</TableTd>
|
||||||
<TableTd>deskripsi</TableTd>
|
<TableTd>
|
||||||
<TableTd>masalah</TableTd>
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.namaIde }} />
|
||||||
<TableTd>benefit</TableTd>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/inovasi/ajukan-ide-inovatif/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table> </Box>
|
</Table>
|
||||||
</Stack>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AjukanIdeInofativ;
|
export default AjukanIdeInovatif;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from "@/con/colors";
|
|
||||||
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from "@mantine/core";
|
|
||||||
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
// import { KeamananEditor } from "../../../keamanan/_com/keamananEditor";
|
|
||||||
|
|
||||||
export default function EditLayananOnlineDesa() {
|
|
||||||
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 Layanan Online Desa</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
|
|
||||||
<IconImageInPicture size={50} />
|
|
||||||
</Box>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Layanan Online Desa</Text>}
|
|
||||||
placeholder='Masukkan nama LayananOnlineDesa'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Layanan Online Desa</Text>
|
|
||||||
{/* <KeamananEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailLayananOnlineDesa() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Layanan Online Desa</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Layanan Online Desa</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/inovasi/layanan-online-desa/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailLayananOnlineDesa;
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/* 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 LayoutTabsLayananOnlineDesa({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Administrasi Online",
|
||||||
|
value: "administrasionline",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/administrasi-online"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jenis Layanan",
|
||||||
|
value: "jenislayanan",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/jenis-layanan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pengaduan Masyarakat",
|
||||||
|
value: "pengaduanmasyarakat",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/pengaduan-masyarakat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Informasi Desa",
|
||||||
|
value: "informasidesa",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/informasi-desa"
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
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}>Layanan Online Desa</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 LayoutTabsLayananOnlineDesa;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
'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, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
|
function DetailAdministrasiOnline() {
|
||||||
|
const beritaState = useProxy(layananonlineDesa)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
beritaState.administrasiOnline.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
beritaState.administrasiOnline.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/inovasi/layanan-online-desa/administrasi-online")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!beritaState.administrasiOnline.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>
|
||||||
|
<Flex gap={"xs"} justify={"space-between"} mt={10}>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Administrasi Online</Text>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (beritaState.administrasiOnline.findUnique.data) {
|
||||||
|
setSelectedId(beritaState.administrasiOnline.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={beritaState.administrasiOnline.delete.loading || !beritaState.administrasiOnline.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
{beritaState.administrasiOnline.findUnique.data ? (
|
||||||
|
<Paper key={beritaState.administrasiOnline.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
|
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
||||||
|
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nomor Telepon</Text>
|
||||||
|
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.nomorTelepon}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Jenis Layanan</Text>
|
||||||
|
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.jenisLayanan?.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus administrasi online ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailAdministrasiOnline;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
|
||||||
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
|
||||||
|
|
||||||
|
function AdministrasiOnline() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Administrasi Online'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListAdministrasiOnline search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListAdministrasiOnline({ search }: { search: string }) {
|
||||||
|
const listState = useProxy(layananonlineDesa.administrasiOnline)
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = listState.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10);
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
const filteredData = (data || []).filter(item => {
|
||||||
|
const keyword = search.toLowerCase();
|
||||||
|
return (
|
||||||
|
item.name.toLowerCase().includes(keyword) ||
|
||||||
|
item.alamat.toLowerCase().includes(keyword) ||
|
||||||
|
item.nomorTelepon.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return <Skeleton h={500} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Title order={3} mb={10}>List Administrasi Online</Title>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Layanan</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody style={{ overflowX: "auto" }}>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>{item.alamat}</TableTd>
|
||||||
|
<TableTd>{item.nomorTelepon}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin//inovasi/layanan-online-desa/administrasi-online/${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 AdministrasiOnline;
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { KeamananEditor } from '../../../keamanan/_com/keamananEditor';
|
|
||||||
|
|
||||||
function CreateLayananOnlineDesa() {
|
|
||||||
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}>Create Layanan Online Desa</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
|
|
||||||
<IconImageInPicture size={50} />
|
|
||||||
</Box>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Layanan Online Desa</Text>}
|
|
||||||
placeholder='Masukkan nama LayananOnlineDesa'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Layanan Online Desa</Text>
|
|
||||||
<KeamananEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreateLayananOnlineDesa;
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
|
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 EditJenisLayanan() {
|
||||||
|
const state = useProxy(layananonlineDesa.jenisLayanan)
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: state.edit.form.nama,
|
||||||
|
deskripsi: state.edit.form.deskripsi,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadJenisLayanan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await state.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading jenis layanan:", error);
|
||||||
|
toast.error("Gagal memuat data jenis layanan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadJenisLayanan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
state.edit.form = {
|
||||||
|
...state.edit.form,
|
||||||
|
nama: formData.nama,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
}
|
||||||
|
await state.edit.update()
|
||||||
|
toast.success("Jenis layanan berhasil diperbarui!")
|
||||||
|
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating jenis layanan:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui jenis layanan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Jenis Layanan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({ ...formData, nama: val.target.value });
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Jenis Layanan</Text>}
|
||||||
|
placeholder="masukkan nama jenis layanan"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditJenisLayanan;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
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 { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function DetailJenisLayanan() {
|
||||||
|
const state = useProxy(layananonlineDesa.jenisLayanan)
|
||||||
|
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/inovasi/layanan-online-desa/jenis-layanan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Jenis Layanan</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"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi }}></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/inovasi/layanan-online-desa/jenis-layanan/${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 jenis layanan ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailJenisLayanan;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
|
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 { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function CreateJenisLayanan() {
|
||||||
|
const router = useRouter();
|
||||||
|
const statePasar = useProxy(layananonlineDesa.jenisLayanan)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
statePasar.findMany.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
statePasar.create.form = {
|
||||||
|
nama: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await statePasar.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<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 Jenis Layanan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={statePasar.create.form.nama}
|
||||||
|
onChange={(val) => {
|
||||||
|
statePasar.create.form.nama = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Layanan</Text>}
|
||||||
|
placeholder='Masukkan nama jenis layanan'
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={statePasar.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
statePasar.create.form.deskripsi = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
|
||||||
|
placeholder='Masukkan deskripsi'
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateJenisLayanan;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } 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 layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
|
||||||
|
|
||||||
|
|
||||||
|
function JenisLayanan() {
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Jenis Layanan'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListJenisLayanan search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListJenisLayanan({ search }: { search: string }) {
|
||||||
|
const stateList = useProxy(layananonlineDesa.jenisLayanan)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateList.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const filteredData = (stateList.findMany.data || []).filter(item => {
|
||||||
|
const keyword = search.toLowerCase();
|
||||||
|
return (
|
||||||
|
item.nama.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!stateList.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Jenis Layanan'
|
||||||
|
href='/admin/inovasi/layanan-online-desa/jenis-layanan/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Jenis Layanan</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.deskripsi}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color="green" onClick={() => router.push(`/admin/inovasi/layanan-online-desa/jenis-layanan/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JenisLayanan;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
'use client'
|
||||||
|
import LayoutTabsLayananOnlineDesa from "./_lib/layoutTabs";
|
||||||
|
|
||||||
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LayoutTabsLayananOnlineDesa>
|
||||||
|
{children}
|
||||||
|
</LayoutTabsLayananOnlineDesa>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
function LayananOnlineDesa() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<HeaderSearch
|
|
||||||
title='Layanan Online Desa'
|
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
/>
|
|
||||||
<ListLayananOnlineDesa/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListLayananOnlineDesa() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Layanan Online Desa'
|
|
||||||
href='/admin/inovasi/layanan-online-desa/create'
|
|
||||||
/>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama Layanan</TableTh>
|
|
||||||
<TableTh>Deskripsi</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
<TableTr>
|
|
||||||
<TableTd>Layanan Online Desa 1</TableTd>
|
|
||||||
<TableTd>Deskripsi Layanan Online Desa 1</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push('/admin/inovasi/layanan-online-desa/detail')}>
|
|
||||||
<IconDeviceImac size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
|
||||||
</TableTbody>
|
|
||||||
</Table>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LayananOnlineDesa;
|
|
||||||
@@ -276,7 +276,7 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "Inovasi_2",
|
id: "Inovasi_2",
|
||||||
name: "Layanan Online Desa",
|
name: "Layanan Online Desa",
|
||||||
path: "/admin/inovasi/layanan-online-desa"
|
path: "/admin/inovasi/layanan-online-desa/administrasi-online"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Inovasi_3",
|
id: "Inovasi_3",
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormCreateAjukanIdeInovatif = Prisma.AjukanIdeInovatifGetPayload<{
|
||||||
|
select: {
|
||||||
|
name: true;
|
||||||
|
deskripsi: true;
|
||||||
|
alamat: true;
|
||||||
|
namaIde: true;
|
||||||
|
masalah: true;
|
||||||
|
benefit: true;
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
export default async function ajukanIdeInovatifCreate(context: Context){
|
||||||
|
const body = context.body as FormCreateAjukanIdeInovatif;
|
||||||
|
|
||||||
|
await prisma.ajukanIdeInovatif.create({
|
||||||
|
data: {
|
||||||
|
name: body.name,
|
||||||
|
deskripsi: body.deskripsi,
|
||||||
|
alamat: body.alamat,
|
||||||
|
namaIde: body.namaIde,
|
||||||
|
masalah: body.masalah,
|
||||||
|
benefit: body.benefit,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success create ajukan ide inovatif",
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function ajukanIdeInovatifDelete(context: Context) {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: "ID tidak diberikan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ajukanIdeInovatif = await prisma.ajukanIdeInovatif.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ajukanIdeInovatif) {
|
||||||
|
return {
|
||||||
|
status: 404,
|
||||||
|
body: "Ajukan ide inovatif tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.ajukanIdeInovatif.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Ajukan ide inovatif berhasil dihapus",
|
||||||
|
status: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function ajukanIdeInovatifFindMany() {
|
||||||
|
try {
|
||||||
|
const data = await prisma.ajukanIdeInovatif.findMany({});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch ajukan ide inovatif",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Find many error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed fetch ajukan ide inovatif",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function ajukanIdeInovatifFindUnique(request: Request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const pathSegments = url.pathname.split("/");
|
||||||
|
const id = pathSegments[pathSegments.length - 1];
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak ditemukan",
|
||||||
|
}, {status: 400});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof id !== 'string') {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak valid",
|
||||||
|
}, {status: 400});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await prisma.ajukanIdeInovatif.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "Ajukan ide inovatif tidak ditemukan",
|
||||||
|
}, {status: 404});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch ajukan ide inovatif by ID",
|
||||||
|
data,
|
||||||
|
}, {status: 200});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Find by ID error:", error);
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil ajukan ide inovatif: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||||
|
}, {status: 500});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import ajukanIdeInovatifCreate from "./create";
|
||||||
|
import ajukanIdeInovatifFindMany from "./findMany";
|
||||||
|
import ajukanIdeInovatifFindUnique from "./findUnique";
|
||||||
|
import ajukanIdeInovatifDelete from "./del";
|
||||||
|
|
||||||
|
const AjukanIdeInovatif = new Elysia({
|
||||||
|
prefix: "/ajukanideinovatif",
|
||||||
|
tags: ["Inovasi/AjukanIde"],
|
||||||
|
})
|
||||||
|
.post("/create", ajukanIdeInovatifCreate, {
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
deskripsi: t.String(),
|
||||||
|
alamat: t.String(),
|
||||||
|
namaIde: t.String(),
|
||||||
|
masalah: t.String(),
|
||||||
|
benefit: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.get("/find-many", ajukanIdeInovatifFindMany)
|
||||||
|
.get("/:id", async (context) => {
|
||||||
|
const response = await ajukanIdeInovatifFindUnique(context.request);
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.delete("/del/:id", ajukanIdeInovatifDelete);
|
||||||
|
export default AjukanIdeInovatif;
|
||||||
@@ -3,6 +3,8 @@ import DesaDigital from "./desa-digital";
|
|||||||
import ProgramKreatif from "./program-kreatif";
|
import ProgramKreatif from "./program-kreatif";
|
||||||
import KolaborasiInovasi from "./kolaborasi-inovasi";
|
import KolaborasiInovasi from "./kolaborasi-inovasi";
|
||||||
import InfoTekno from "./info-teknologi";
|
import InfoTekno from "./info-teknologi";
|
||||||
|
import AjukanIdeInovatif from "./ajukan-ide-inovatif";
|
||||||
|
import LayananOnlineDesa from "./layanan-online-desa";
|
||||||
|
|
||||||
const Inovasi = new Elysia({
|
const Inovasi = new Elysia({
|
||||||
prefix: "/api/inovasi",
|
prefix: "/api/inovasi",
|
||||||
@@ -12,5 +14,7 @@ const Inovasi = new Elysia({
|
|||||||
.use(ProgramKreatif)
|
.use(ProgramKreatif)
|
||||||
.use(KolaborasiInovasi)
|
.use(KolaborasiInovasi)
|
||||||
.use(InfoTekno)
|
.use(InfoTekno)
|
||||||
|
.use(AjukanIdeInovatif)
|
||||||
|
.use(LayananOnlineDesa)
|
||||||
|
|
||||||
export default Inovasi;
|
export default Inovasi;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormCreate = {
|
||||||
|
name: string;
|
||||||
|
alamat: string;
|
||||||
|
nomorTelepon: string;
|
||||||
|
jenisLayananId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function administrasiOnlineCreate(context: Context) {
|
||||||
|
const body = context.body as FormCreate;
|
||||||
|
|
||||||
|
if (!body.jenisLayananId) {
|
||||||
|
throw new Error("jenisLayananId wajib diisi");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create langsung data AdministrasiOnline
|
||||||
|
const result = await prisma.administrasiOnline.create({
|
||||||
|
data: {
|
||||||
|
name: body.name,
|
||||||
|
alamat: body.alamat,
|
||||||
|
nomorTelepon: body.nomorTelepon,
|
||||||
|
jenisLayananId: body.jenisLayananId, // relasi ke JenisLayanan
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
jenisLayanan: true, // Include data relasi
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil membuat administrasi online",
|
||||||
|
data: result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating administrasi online:", error);
|
||||||
|
throw new Error(
|
||||||
|
"Gagal membuat administrasi online: " + (error as Error).message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function administrasiOnlineDelete(context: Context) {
|
||||||
|
const { params } = context;
|
||||||
|
const id = params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("ID tidak ditemukan dalam parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await prisma.administrasiOnline.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil menghapus administrasi online",
|
||||||
|
data: deleted,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// /api/berita/findManyPaginated.ts
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function administrasiOnlineFindMany(context: Context) {
|
||||||
|
const page = Number(context.query.page) || 1;
|
||||||
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.administrasiOnline.findMany({
|
||||||
|
where: { isActive: true },
|
||||||
|
include: {
|
||||||
|
jenisLayanan: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||||
|
}),
|
||||||
|
prisma.administrasiOnline.count({
|
||||||
|
where: { isActive: true }
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch administrasi online with pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Find many paginated error:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed fetch administrasi online with pagination",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default administrasiOnlineFindMany;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function administrasiOnlineFindUnique(context: Context) {
|
||||||
|
const { params } = context;
|
||||||
|
const id = params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("ID tidak ditemukan dalam parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await prisma.administrasiOnline.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
jenisLayanan: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new Error("Administrasi online tidak ditemukan");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Data administrasi online ditemukan",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import JenisLayanan from "./jenis-layanan";
|
||||||
|
import administrasiOnlineFindMany from "./findMany";
|
||||||
|
import administrasiOnlineFindUnique from "./findUnique";
|
||||||
|
import administrasiOnlineDelete from "./del";
|
||||||
|
import administrasiOnlineCreate from "./create";
|
||||||
|
import { t } from "elysia";
|
||||||
|
|
||||||
|
const AdministrasiOnline = new Elysia({
|
||||||
|
prefix: "/administrasionline",
|
||||||
|
tags: ["Inovasi/Layanan Online Desa/Administrasi Online"],
|
||||||
|
})
|
||||||
|
.get("/find-many", administrasiOnlineFindMany)
|
||||||
|
.get("/:id", administrasiOnlineFindUnique)
|
||||||
|
.delete("/del/:id", administrasiOnlineDelete)
|
||||||
|
.post("/create", administrasiOnlineCreate, {
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
alamat: t.String(),
|
||||||
|
nomorTelepon: t.String(),
|
||||||
|
jenisLayananId: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.use(JenisLayanan);
|
||||||
|
|
||||||
|
export default AdministrasiOnline;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
|
||||||
|
export default async function jenisLayananCreate(context: Context) {
|
||||||
|
const body = context.body as {nama: string, deskripsi: string};
|
||||||
|
|
||||||
|
if (!body.nama) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Nama is required",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const jenisLayanan = await prisma.jenisLayanan.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
deskripsi: body.deskripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success create jenis layanan",
|
||||||
|
data: jenisLayanan
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
const jenisLayananDelete = async (context: Context) => {
|
||||||
|
const id = context.params.id;
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID is required",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const jenisLayanan = await prisma.jenisLayanan.delete({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if(!jenisLayanan) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Jenis layanan tidak ditemukan",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success delete jenis layanan",
|
||||||
|
data: jenisLayanan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default jenisLayananDelete
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function jenisLayananFindMany() {
|
||||||
|
const data = await prisma.jenisLayanan.findMany();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Context } from "elysia";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function jenisLayananFindUnique(context: Context) {
|
||||||
|
const url = new URL(context.request.url);
|
||||||
|
const pathSegments = url.pathname.split('/');
|
||||||
|
const id = pathSegments[pathSegments.length - 1];
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID is required",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof id !== 'string') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID is required",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await prisma.jenisLayanan.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Jenis layanan tidak ditemukan",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success find jenis layanan",
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Find by ID error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil jenis layanan: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import jenisLayananFindMany from "./findMany";
|
||||||
|
import jenisLayananFindUnique from "./findUnique";
|
||||||
|
import jenisLayananDelete from "./del";
|
||||||
|
import jenisLayananCreate from "./create";
|
||||||
|
import jenisLayananUpdate from "./updt";
|
||||||
|
import { t } from "elysia";
|
||||||
|
|
||||||
|
const JenisLayanan = new Elysia({
|
||||||
|
prefix: "/jenislayanan",
|
||||||
|
tags: ["Inovasi/Jenis Layanan"],
|
||||||
|
})
|
||||||
|
.get("/find-many", jenisLayananFindMany)
|
||||||
|
.get("/:id", async (context) => {
|
||||||
|
const response = await jenisLayananFindUnique(context);
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.delete("/del/:id", jenisLayananDelete)
|
||||||
|
.post("/create", jenisLayananCreate, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
deskripsi: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.put("/:id", jenisLayananUpdate, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
deskripsi: t.String(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default JenisLayanan;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function jenisLayananUpdate(context: Context) {
|
||||||
|
const body = context.body as { nama: string, deskripsi: string };
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
// Validasi ID dan nama
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID is required",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!body.nama) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Nama is required",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jenisLayanan = await prisma.jenisLayanan.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
deskripsi: body.deskripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success update jenis layanan",
|
||||||
|
data: jenisLayanan,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal update jenis layanan",
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import AdministrasiOnline from "./administrasi-online";
|
||||||
|
|
||||||
|
|
||||||
|
const LayananOnlineDesa = new Elysia({
|
||||||
|
prefix: "/layananonlinedesa",
|
||||||
|
tags: ["Inovasi/Layanan Online Desa"],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}).use(AdministrasiOnline);
|
||||||
|
|
||||||
|
export default LayananOnlineDesa;
|
||||||
@@ -1,10 +1,40 @@
|
|||||||
|
'use client'
|
||||||
|
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Text, SimpleGrid, Paper, List, ListItem, Flex, ActionIcon } from '@mantine/core';
|
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
|
||||||
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
|
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const ideInovatif = useProxy(ajukanIdeInovatifState)
|
||||||
|
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
// Reset state di valtio
|
||||||
|
ideInovatif.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
alamat: "",
|
||||||
|
namaIde: "",
|
||||||
|
masalah: "",
|
||||||
|
benefit: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset state lokal
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// Submit data berita
|
||||||
|
await ideInovatif.create.create();
|
||||||
|
|
||||||
|
// Reset form setelah submit
|
||||||
|
resetForm();
|
||||||
|
close();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
@@ -40,7 +70,7 @@ function Page() {
|
|||||||
<IconArrowRight size={30} color={colors['blue-button']} />
|
<IconArrowRight size={30} color={colors['blue-button']} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 5, md: 10 }} py={5}>
|
<Box px={{ base: 5, md: 10 }} py={5}>
|
||||||
<ActionIcon variant="transparent" size={150}>
|
<ActionIcon variant="transparent" size={150} onClick={open}>
|
||||||
<IconBulbFilled size={150} color={colors['blue-button']} />
|
<IconBulbFilled size={150} color={colors['blue-button']} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -49,6 +79,65 @@ function Page() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
radius={0}
|
||||||
|
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||||
|
>
|
||||||
|
<Paper p={"md"} withBorder>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Ajukan Ide Inovatif</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
onChange={(val) => {
|
||||||
|
ideInovatif.create.form.name = val.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
onChange={(val) => {
|
||||||
|
ideInovatif.create.form.alamat = val.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>}
|
||||||
|
placeholder="masukkan nama ide"
|
||||||
|
onChange={(val) => {
|
||||||
|
ideInovatif.create.form.namaIde = val.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={ideInovatif.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
ideInovatif.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>}
|
||||||
|
placeholder="masukkan masalah"
|
||||||
|
onChange={(val) => {
|
||||||
|
ideInovatif.create.form.masalah = val.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>}
|
||||||
|
placeholder="masukkan benefit"
|
||||||
|
onChange={(val) => {
|
||||||
|
ideInovatif.create.form.benefit = val.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { IconFileCheckFilled } from '@tabler/icons-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function AdministrasiOnline() {
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const state = useProxy(layananonlineDesa);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// ✅ Panggil load data jenis layanan dari backend
|
||||||
|
if (!state.jenisLayanan.findMany.data) {
|
||||||
|
state.jenisLayanan.findMany.load();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
state.administrasiOnline.create.form = {
|
||||||
|
name: '',
|
||||||
|
alamat: '',
|
||||||
|
nomorTelepon: '',
|
||||||
|
jenisLayananId: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await state.administrasiOnline.create.create();
|
||||||
|
resetForm();
|
||||||
|
close(); // Tutup modal setelah submit
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack>
|
||||||
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.8 }} onClick={open}>
|
||||||
|
<Paper p="xl" withBorder>
|
||||||
|
<Box>
|
||||||
|
<IconFileCheckFilled size={50} color={colors['blue-button']} />
|
||||||
|
</Box>
|
||||||
|
<Text fz="h3" fw="bold" c={colors['blue-button']}>
|
||||||
|
Administrasi Online
|
||||||
|
</Text>
|
||||||
|
<Text fz="lg" c="black">
|
||||||
|
Pengurusan surat dan dokumen secara digital tanpa perlu datang ke kantor desa
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
</motion.div>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
radius={0}
|
||||||
|
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||||
|
>
|
||||||
|
<Paper p="md" withBorder>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Ajukan Administrasi Online</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
onChange={(val) => (state.administrasiOnline.create.form.name = val.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
onChange={(val) => (state.administrasiOnline.create.form.alamat = val.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label={<Text fz="sm" fw="bold">Nomor Telepon</Text>}
|
||||||
|
placeholder="masukkan nomor telepon"
|
||||||
|
onChange={(val) => (state.administrasiOnline.create.form.nomorTelepon = val.target.value)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={state.administrasiOnline.create.form.jenisLayananId}
|
||||||
|
onChange={(val) => {
|
||||||
|
state.administrasiOnline.create.form.jenisLayananId = val ?? "";
|
||||||
|
}}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Jenis Layanan</Text>}
|
||||||
|
placeholder="Pilih kategori produk"
|
||||||
|
data={
|
||||||
|
state.jenisLayanan.findMany.data?.map((v) => ({
|
||||||
|
value: v.id,
|
||||||
|
label: v.nama,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdministrasiOnline;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Text } from '@mantine/core';
|
||||||
|
import { IconBell } from '@tabler/icons-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
function InformasiDesa() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack >
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.8 }}
|
||||||
|
>
|
||||||
|
<Paper p={'xl'} >
|
||||||
|
<Box>
|
||||||
|
<IconBell size={50} color={colors['blue-button']} />,
|
||||||
|
</Box>
|
||||||
|
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Informasi Desa</Text>
|
||||||
|
<Text fz={'lg'} c={'black'}>Akses berita dan pengumuman terbaru seputar kegiatan desa</Text>
|
||||||
|
</Paper>
|
||||||
|
</motion.div>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InformasiDesa;
|
||||||
@@ -1,28 +1,11 @@
|
|||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Box, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||||
import { IconBell, IconFileCheckFilled, IconMessageCircleQuestion } from '@tabler/icons-react';
|
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import AdministrasiOnline from './administrasi-online/page';
|
||||||
|
import InformasiDesa from './informasi-desa/page';
|
||||||
|
import PengaduanMasyarakat from './pengaduan-masyarakat/page';
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: <IconFileCheckFilled size={50} color={colors['blue-button']} />,
|
|
||||||
judul: 'Administrasi Online',
|
|
||||||
deskripsi: 'Pengurusan surat dan dokumen secara digital tanpa perlu datang ke kantor desa'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
icon: <IconMessageCircleQuestion size={50} color={colors['blue-button']} />,
|
|
||||||
judul: 'Pengaduan Masyarakat',
|
|
||||||
deskripsi: 'Sampaikan keluhan dan aspirasi Anda melalui platform digital kami'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
icon: <IconBell size={50} color={colors['blue-button']} />,
|
|
||||||
judul: 'Informasi Desa',
|
|
||||||
deskripsi: 'Akses berita dan pengumuman terbaru seputar kegiatan desa'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
@@ -37,26 +20,16 @@ function Page() {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'} justify='center'>
|
<Stack gap={'lg'} justify='center'>
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
pb={10}
|
|
||||||
cols={{
|
cols={{
|
||||||
base: 1,
|
base: 1,
|
||||||
md: 1
|
md: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.map((v, k) => {
|
<Stack gap={'lg'}>
|
||||||
return (
|
<AdministrasiOnline />
|
||||||
<Stack key={k} >
|
<PengaduanMasyarakat />
|
||||||
|
<InformasiDesa />
|
||||||
<Paper p={'xl'} >
|
|
||||||
<Box>
|
|
||||||
{v.icon}
|
|
||||||
</Box>
|
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.judul}</Text>
|
|
||||||
<Text fz={'lg'} c={'black'}>{v.deskripsi}</Text>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Text } from '@mantine/core';
|
||||||
|
import { IconMessageCircleQuestion } from '@tabler/icons-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
function PengaduanMasyarakat() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack >
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.8 }}
|
||||||
|
>
|
||||||
|
<Paper p={'xl'} >
|
||||||
|
<Box>
|
||||||
|
<IconMessageCircleQuestion size={50} color={colors['blue-button']} />,
|
||||||
|
</Box>
|
||||||
|
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Pengaduan Masyarakat</Text>
|
||||||
|
<Text fz={'lg'} c={'black'}>Sampaikan keluhan dan aspirasi Anda melalui platform digital kami</Text>
|
||||||
|
</Paper>
|
||||||
|
</motion.div>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PengaduanMasyarakat;
|
||||||
Reference in New Issue
Block a user