FIX UI & API Menu Desa, Submenu Pelayanan Surat Keterangan
This commit is contained in:
@@ -66,7 +66,8 @@ model FileStorage {
|
|||||||
Posyandu Posyandu[]
|
Posyandu Posyandu[]
|
||||||
StrukturPPID StrukturPPID[]
|
StrukturPPID StrukturPPID[]
|
||||||
GalleryFoto GalleryFoto[]
|
GalleryFoto GalleryFoto[]
|
||||||
PelayananSuratKeterangan PelayananSuratKeterangan[]
|
|
||||||
|
Pelapor Pelapor[]
|
||||||
Penghargaan Penghargaan[]
|
Penghargaan Penghargaan[]
|
||||||
ProfileDesaImage ProfileDesaImage[]
|
ProfileDesaImage ProfileDesaImage[]
|
||||||
ProfilePPID ProfilePPID[]
|
ProfilePPID ProfilePPID[]
|
||||||
@@ -78,7 +79,8 @@ model FileStorage {
|
|||||||
InfoWabahPenyakit InfoWabahPenyakit[]
|
InfoWabahPenyakit InfoWabahPenyakit[]
|
||||||
KeamananLingkungan KeamananLingkungan[]
|
KeamananLingkungan KeamananLingkungan[]
|
||||||
MenuTipsKeamanan MenuTipsKeamanan[]
|
MenuTipsKeamanan MenuTipsKeamanan[]
|
||||||
Pelapor Pelapor[]
|
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
|
||||||
|
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
|
||||||
PasarDesa PasarDesa[]
|
PasarDesa PasarDesa[]
|
||||||
KontakDaruratKeamanan KontakDaruratKeamanan[]
|
KontakDaruratKeamanan KontakDaruratKeamanan[]
|
||||||
KontakItem KontakItem[]
|
KontakItem KontakItem[]
|
||||||
@@ -100,6 +102,7 @@ model FileStorage {
|
|||||||
DataPerpustakaan DataPerpustakaan[]
|
DataPerpustakaan DataPerpustakaan[]
|
||||||
|
|
||||||
PegawaiPPID PegawaiPPID[]
|
PegawaiPPID PegawaiPPID[]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU LANDING PAGE ========================================= //
|
//========================================= MENU LANDING PAGE ========================================= //
|
||||||
@@ -130,15 +133,15 @@ model ProgramInovasi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model MediaSosial {
|
model MediaSosial {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
iconUrl String? @db.VarChar(255)
|
iconUrl String? @db.VarChar(255)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= PROFILE ========================================= //
|
//========================================= PROFILE ========================================= //
|
||||||
@@ -148,7 +151,7 @@ model DesaAntiKorupsi {
|
|||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
|
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
|
||||||
kategoriId String
|
kategoriId String
|
||||||
file FileStorage? @relation(fields: [fileId], references: [id])
|
file FileStorage? @relation(fields: [fileId], references: [id])
|
||||||
fileId String?
|
fileId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -168,30 +171,30 @@ model KategoriDesaAntiKorupsi {
|
|||||||
|
|
||||||
//========================================= SDGS Desa ========================================= //
|
//========================================= SDGS Desa ========================================= //
|
||||||
model SDGSDesa {
|
model SDGSDesa {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
jumlah String
|
jumlah String
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= APBDes ========================================= //
|
//========================================= APBDes ========================================= //
|
||||||
model APBDes {
|
model APBDes {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
jumlah String
|
jumlah String
|
||||||
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
|
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id])
|
file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id])
|
||||||
fileId String?
|
fileId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= PRESTASI DESA ========================================= //
|
//========================================= PRESTASI DESA ========================================= //
|
||||||
@@ -313,14 +316,14 @@ model StrukturPPID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model PosisiOrganisasiPPID {
|
model PosisiOrganisasiPPID {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
nama String @db.VarChar(100)
|
nama String @db.VarChar(100)
|
||||||
deskripsi String? @db.Text
|
deskripsi String? @db.Text
|
||||||
hierarki Int
|
hierarki Int
|
||||||
pegawai PegawaiPPID[]
|
pegawai PegawaiPPID[]
|
||||||
strukturOrganisasi StrukturPPID[] // Relasi balik
|
strukturOrganisasi StrukturPPID[] // Relasi balik
|
||||||
parentId String?
|
parentId String?
|
||||||
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
|
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
|
||||||
children PosisiOrganisasiPPID[] @relation("Parent")
|
children PosisiOrganisasiPPID[] @relation("Parent")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,15 +675,17 @@ model GalleryVideo {
|
|||||||
|
|
||||||
// ========================================= LAYANAN DESA ========================================= //
|
// ========================================= LAYANAN DESA ========================================= //
|
||||||
model PelayananSuratKeterangan {
|
model PelayananSuratKeterangan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
||||||
imageId String
|
imageId String?
|
||||||
createdAt DateTime @default(now())
|
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
||||||
updatedAt DateTime @updatedAt
|
image2Id String?
|
||||||
deletedAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
model PelayananTelunjukSaktiDesa {
|
model PelayananTelunjukSaktiDesa {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -8,12 +9,14 @@ const templateSuratKeteranganForm = z.object({
|
|||||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
imageId: z.string().nonempty(),
|
imageId: z.string().nonempty(),
|
||||||
|
image2Id: z.string().nonempty(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const suratKeteranganForm = {
|
const suratKeteranganForm = {
|
||||||
name: "",
|
name: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
|
image2Id: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const telunjukSaktiDesaForm = {
|
const telunjukSaktiDesaForm = {
|
||||||
@@ -105,15 +108,38 @@ const suratKeterangan = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: [] as Prisma.PelayananSuratKeteranganGetPayload<{
|
data: null as any[] | null,
|
||||||
include: { image: true };
|
page: 1,
|
||||||
}>[],
|
totalPages: 1,
|
||||||
async load() {
|
total: 0,
|
||||||
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
|
loading: false,
|
||||||
"find-many"
|
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||||
].get();
|
suratKeterangan.findMany.loading = true; // Use the full path to access the property
|
||||||
if (res.status === 200) {
|
suratKeterangan.findMany.page = page;
|
||||||
suratKeterangan.findMany.data = res.data?.data ?? [];
|
try {
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query: { page, limit },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
suratKeterangan.findMany.data = res.data.data || [];
|
||||||
|
suratKeterangan.findMany.total = res.data.total || 0;
|
||||||
|
suratKeterangan.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load surat keterangan:", res.data?.message);
|
||||||
|
suratKeterangan.findMany.data = [];
|
||||||
|
suratKeterangan.findMany.total = 0;
|
||||||
|
suratKeterangan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading surat keterangan:", error);
|
||||||
|
suratKeterangan.findMany.data = [];
|
||||||
|
suratKeterangan.findMany.total = 0;
|
||||||
|
suratKeterangan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
suratKeterangan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -121,6 +147,7 @@ const suratKeterangan = proxy({
|
|||||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
|
image2: true;
|
||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
@@ -202,6 +229,7 @@ const suratKeterangan = proxy({
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi,
|
||||||
imageId: data.imageId || "",
|
imageId: data.imageId || "",
|
||||||
|
image2Id: data.image2Id || "",
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -238,6 +266,7 @@ const suratKeterangan = proxy({
|
|||||||
name: this.form.name,
|
name: this.form.name,
|
||||||
deskripsi: this.form.deskripsi,
|
deskripsi: this.form.deskripsi,
|
||||||
imageId: this.form.imageId,
|
imageId: this.form.imageId,
|
||||||
|
image2Id: this.form.image2Id,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -16,11 +17,14 @@ function EditSuratKeterangan() {
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [file2, setFile2] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateSurat.edit.form.name,
|
name: stateSurat.edit.form.name,
|
||||||
deskripsi: stateSurat.edit.form.deskripsi,
|
deskripsi: stateSurat.edit.form.deskripsi,
|
||||||
imageId: stateSurat.edit.form.imageId,
|
imageId: stateSurat.edit.form.imageId,
|
||||||
|
image2Id: stateSurat.edit.form.image2Id,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -31,12 +35,22 @@ function EditSuratKeterangan() {
|
|||||||
const data = await stateSurat.edit.load(id);
|
const data = await stateSurat.edit.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name,
|
name: data.name || "",
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi || "",
|
||||||
imageId: data.imageId,
|
imageId: data.imageId || "",
|
||||||
|
image2Id: data.image2Id || "",
|
||||||
});
|
});
|
||||||
if (data?.image?.link) {
|
|
||||||
|
if (data.image?.link) {
|
||||||
setPreviewImage(data.image.link);
|
setPreviewImage(data.image.link);
|
||||||
|
} else {
|
||||||
|
setPreviewImage(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.image2?.link) {
|
||||||
|
setPreviewImage2(data.image2.link);
|
||||||
|
} else {
|
||||||
|
setPreviewImage2(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -54,6 +68,7 @@ function EditSuratKeterangan() {
|
|||||||
name: formData.name,
|
name: formData.name,
|
||||||
deskripsi: formData.deskripsi,
|
deskripsi: formData.deskripsi,
|
||||||
imageId: formData.imageId,
|
imageId: formData.imageId,
|
||||||
|
image2Id: formData.image2Id,
|
||||||
}
|
}
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
@@ -66,6 +81,17 @@ function EditSuratKeterangan() {
|
|||||||
stateSurat.edit.form.imageId = uploaded.id;
|
stateSurat.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file2) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
stateSurat.edit.form.image2Id = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
await stateSurat.edit.update()
|
await stateSurat.edit.update()
|
||||||
toast.success("Surat berhasil diperbarui!")
|
toast.success("Surat berhasil diperbarui!")
|
||||||
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
||||||
@@ -103,25 +129,106 @@ function EditSuratKeterangan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box >
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const file = files[0]; // Hanya ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (file) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(file);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(file)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
maxSize={5 * 1024 ** 2} // 5MB
|
||||||
{previewImage ? (
|
accept={{
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||||
) : (
|
}}
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
>
|
||||||
<IconImageInPicture />
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
</Center>
|
<Dropzone.Accept>
|
||||||
)}
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag images here or click to select files
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Attach as many files as you like, each file should not exceed 5mb
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
{previewImage && (
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
width={280}
|
||||||
|
height={180}
|
||||||
|
fit="cover"
|
||||||
|
radius="sm"
|
||||||
|
mt="md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box >
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const file = files[0]; // Hanya ambil file pertama
|
||||||
|
if (file) {
|
||||||
|
setFile2(file);
|
||||||
|
setPreviewImage2(URL.createObjectURL(file)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
maxSize={5 * 1024 ** 2} // 5MB
|
||||||
|
accept={{
|
||||||
|
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag images here or click to select files
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Attach as many files as you like, each file should not exceed 5mb
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
{previewImage2 && (
|
||||||
|
<Image
|
||||||
|
src={previewImage2}
|
||||||
|
alt="Preview"
|
||||||
|
width={280}
|
||||||
|
height={180}
|
||||||
|
fit="cover"
|
||||||
|
radius="sm"
|
||||||
|
mt="md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ function DetailSuratKeterangan() {
|
|||||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image?.link} alt="gambar" />
|
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image?.link} alt="gambar" />
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image2?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
<Flex gap={"xs"} mt={10}>
|
<Flex gap={"xs"} mt={10}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -12,8 +13,8 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
function CreateSuratKeterangan() {
|
function CreateSuratKeterangan() {
|
||||||
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage2, setPreviewImage2] = useState<{ preview: string; file: File } | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [previewImage, setPreviewImage] = useState<{ preview: string; file: File } | null>(null);
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@@ -21,33 +22,57 @@ function CreateSuratKeterangan() {
|
|||||||
name: "",
|
name: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
|
image2Id: ""
|
||||||
}
|
}
|
||||||
setPreviewImage(null)
|
setPreviewImage(null)
|
||||||
setFile(null)
|
setPreviewImage2(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!previewImage) {
|
||||||
return toast.error("Silahkan pilih file gambar terlebih dahulu")
|
return toast.warn("Pilih file gambar utama terlebih dahulu");
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
try {
|
||||||
file: file,
|
// Upload gambar utama
|
||||||
name: file.name
|
const res1 = await ApiFetch.api.fileStorage.create.post({
|
||||||
})
|
file: previewImage.file,
|
||||||
|
name: `main_${previewImage.file.name}`,
|
||||||
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data
|
const uploadedImage1 = res1.data?.data;
|
||||||
if (!uploaded?.id) {
|
if (!uploadedImage1?.id) {
|
||||||
return toast.error("Gagal upload gambar")
|
return toast.error("Gagal upload gambar utama");
|
||||||
|
}
|
||||||
|
|
||||||
|
let uploadedImage2 = null;
|
||||||
|
// Upload gambar kedua jika ada
|
||||||
|
if (previewImage2) {
|
||||||
|
const res2 = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file: previewImage2.file,
|
||||||
|
name: `secondary_${previewImage2.file.name}`,
|
||||||
|
});
|
||||||
|
uploadedImage2 = res2.data?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set form data
|
||||||
|
stateSurat.create.form.imageId = uploadedImage1.id;
|
||||||
|
if (uploadedImage2?.id) {
|
||||||
|
stateSurat.create.form.image2Id = uploadedImage2.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the record
|
||||||
|
await stateSurat.create.create();
|
||||||
|
|
||||||
|
// Reset form dan redirect
|
||||||
|
resetForm();
|
||||||
|
toast.success("Data surat keterangan berhasil ditambahkan");
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_surat_keterangan");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating surat keterangan:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menambahkan surat keterangan");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
stateSurat.create.form.imageId = uploaded.id
|
|
||||||
|
|
||||||
await stateSurat.create.create()
|
|
||||||
resetForm()
|
|
||||||
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
|
||||||
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
@@ -75,25 +100,105 @@ function CreateSuratKeterangan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Utama</Text>
|
||||||
value={file}
|
<Dropzone
|
||||||
onChange={async (e) => {
|
onDrop={(files) => {
|
||||||
if (!e) return;
|
const file = files[0];
|
||||||
setFile(e);
|
if (file) {
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
setPreviewImage({
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
file,
|
||||||
);
|
preview: URL.createObjectURL(file)
|
||||||
setPreviewImage(base64);
|
});
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{
|
||||||
) : (
|
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
}}
|
||||||
<IconImageInPicture />
|
>
|
||||||
</Center>
|
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
|
||||||
)}
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7} display="block">
|
||||||
|
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
{previewImage && (
|
||||||
|
<Image
|
||||||
|
src={previewImage.preview}
|
||||||
|
alt="Preview Gambar Utama"
|
||||||
|
width={280}
|
||||||
|
height={180}
|
||||||
|
fit="cover"
|
||||||
|
radius="sm"
|
||||||
|
mt="md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt="lg">
|
||||||
|
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Tambahan (Opsional)</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const file = files[0];
|
||||||
|
if (file) {
|
||||||
|
setPreviewImage2({
|
||||||
|
file,
|
||||||
|
preview: URL.createObjectURL(file)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{
|
||||||
|
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7} display="block">
|
||||||
|
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
{previewImage2 ? (
|
||||||
|
<Image
|
||||||
|
src={previewImage2.preview}
|
||||||
|
alt="Preview Gambar Tambahan"
|
||||||
|
width={280}
|
||||||
|
height={180}
|
||||||
|
fit="cover"
|
||||||
|
radius="sm"
|
||||||
|
mt="md"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text size="sm" c="dimmed" mt="sm">
|
||||||
|
Kosongkan jika tidak ada gambar tambahan
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
|
||||||
function SuratKeterangan() {
|
function SuratKeterangan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Posisi Organisasi'
|
title='Pelayanan Surat Keterangan'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -30,53 +30,89 @@ function ListSuratKeterangan({ search }: { search: string }) {
|
|||||||
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
|
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useShallowEffect(() => {
|
const {
|
||||||
suratKeteranganState.findMany.load()
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = suratKeteranganState.findMany;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const filteredData = (suratKeteranganState.findMany.data || []).filter(item => {
|
const filteredData = useMemo(() => {
|
||||||
const keyword = search.toLowerCase();
|
if (!data) return [];
|
||||||
return (
|
return data.filter(item => {
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
const keyword = search.toLowerCase();
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
return (
|
||||||
);
|
item.name?.toLowerCase().includes(keyword) ||
|
||||||
});
|
item.deskripsi?.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
|
||||||
|
}, [data, search]);
|
||||||
|
|
||||||
if (!suratKeteranganState.findMany.data) {
|
// Handle loading state
|
||||||
return (
|
if (loading || !data) {
|
||||||
<Stack py={10}>
|
return (
|
||||||
<Skeleton h={500} />
|
<Stack py={10}>
|
||||||
</Stack>
|
<Skeleton height={300} />
|
||||||
)
|
</Stack>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Surat Keterangan'
|
||||||
|
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
|
||||||
|
/>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
title='List Surat Keterangan'
|
title='List Surat Keterangan'
|
||||||
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
|
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={200}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="gambar" />
|
<Box w={300}>
|
||||||
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>
|
<Text>
|
||||||
@@ -90,6 +126,18 @@ function ListSuratKeterangan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type FormCreate = Prisma.PelayananSuratKeteranganGetPayload<{
|
|||||||
name: true;
|
name: true;
|
||||||
deskripsi: true;
|
deskripsi: true;
|
||||||
imageId: true;
|
imageId: true;
|
||||||
|
image2Id: true;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
async function createPelayananSuratKeterangan(context: Context) {
|
async function createPelayananSuratKeterangan(context: Context) {
|
||||||
@@ -17,6 +18,7 @@ async function createPelayananSuratKeterangan(context: Context) {
|
|||||||
name: body.name,
|
name: body.name,
|
||||||
deskripsi: body.deskripsi,
|
deskripsi: body.deskripsi,
|
||||||
imageId: body.imageId,
|
imageId: body.imageId,
|
||||||
|
image2Id: body.image2Id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
image: true,
|
image: true,
|
||||||
|
image2: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,6 +41,18 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pelayananSuratKeterangan.image2) {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(pelayananSuratKeterangan.image2.path, pelayananSuratKeterangan.image2.name);
|
||||||
|
await fs.unlink(filePath);
|
||||||
|
await prisma.fileStorage.delete({
|
||||||
|
where: { id: pelayananSuratKeterangan.image2.id },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal hapus gambar lama:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const deleted = await prisma.pelayananSuratKeterangan.delete({
|
const deleted = await prisma.pelayananSuratKeterangan.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,47 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function pelayananSuratKeteranganFindMany() {
|
export default async function pelayananSuratKeteranganFindMany(context: Context) {
|
||||||
try {
|
const page = Number(context.query.page) || 1;
|
||||||
const data = await prisma.pelayananSuratKeterangan.findMany({
|
const limit = Number(context.query.limit) || 10;
|
||||||
where: { isActive: true },
|
const skip = (page - 1) * limit;
|
||||||
include: {
|
|
||||||
image: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
try {
|
||||||
success: true,
|
const [data, total] = await Promise.all([
|
||||||
message: "Success fetch pelayanan surat keterangan",
|
prisma.pelayananSuratKeterangan.findMany({
|
||||||
data,
|
where: { isActive: true },
|
||||||
};
|
include: {
|
||||||
} catch (e) {
|
image: true,
|
||||||
console.error("Find many error:", e);
|
image2: true,
|
||||||
return {
|
},
|
||||||
success: false,
|
skip,
|
||||||
message: "Failed fetch pelayanan surat keterangan",
|
take: limit,
|
||||||
};
|
orderBy: { createdAt: 'desc' },
|
||||||
}
|
}),
|
||||||
|
prisma.pelayananSuratKeterangan.count({
|
||||||
|
where: { isActive: true }
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(total / limit);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch pelayanan surat keterangan with pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Find many paginated error:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed fetch pelayanan surat keterangan with pagination",
|
||||||
|
data: [],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ export default async function pelayananSuratKeteranganFindUnique(request: Reques
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
image: true,
|
image: true,
|
||||||
|
image2: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
|
|||||||
name: t.String(),
|
name: t.String(),
|
||||||
deskripsi: t.String(),
|
deskripsi: t.String(),
|
||||||
imageId: t.String(),
|
imageId: t.String(),
|
||||||
|
image2Id: t.String(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.delete("/del/:id", pelayananSuratKeteranganDelete)
|
.delete("/del/:id", pelayananSuratKeteranganDelete)
|
||||||
@@ -30,6 +31,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
|
|||||||
name: t.String(),
|
name: t.String(),
|
||||||
deskripsi: t.String(),
|
deskripsi: t.String(),
|
||||||
imageId: t.String(),
|
imageId: t.String(),
|
||||||
|
image2Id: t.String(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
export default PelayananSuratKeterangan;
|
export default PelayananSuratKeterangan;
|
||||||
@@ -9,6 +9,7 @@ type FormUpdate = Prisma.PelayananSuratKeteranganGetPayload<{
|
|||||||
name: true;
|
name: true;
|
||||||
deskripsi: true;
|
deskripsi: true;
|
||||||
imageId: true;
|
imageId: true;
|
||||||
|
image2Id: true;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
export default async function updatePelayananSuratKeterangan(context: Context) {
|
export default async function updatePelayananSuratKeterangan(context: Context) {
|
||||||
@@ -16,7 +17,7 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
|
|||||||
const id = context.params?.id;
|
const id = context.params?.id;
|
||||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||||
|
|
||||||
const { name, deskripsi, imageId } = body;
|
const { name, deskripsi, imageId, image2Id } = body;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
@@ -63,12 +64,28 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existing.image2Id && existing.image2Id !== image2Id) {
|
||||||
|
const oldImage = existing.image;
|
||||||
|
if (oldImage) {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(oldImage.path, oldImage.name);
|
||||||
|
await fs.unlink(filePath);
|
||||||
|
await prisma.fileStorage.delete({
|
||||||
|
where: { id: oldImage.id },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal hapus gambar lama:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updated = await prisma.pelayananSuratKeterangan.update({
|
const updated = await prisma.pelayananSuratKeterangan.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
deskripsi,
|
deskripsi,
|
||||||
imageId,
|
imageId,
|
||||||
|
image2Id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user