diff --git a/src/components/DesaSetting.tsx b/src/components/DesaSetting.tsx index 6a5ba29..9bb41ce 100644 --- a/src/components/DesaSetting.tsx +++ b/src/components/DesaSetting.tsx @@ -16,11 +16,11 @@ import { } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit } from "@tabler/icons-react"; +import _ from "lodash"; import { useState } from "react"; import useSWR from "swr"; import ModalFile from "./ModalFile"; import notification from "./notificationGlobal"; -import _ from "lodash"; export default function DesaSetting() { const [btnDisable, setBtnDisable] = useState(false); @@ -50,7 +50,8 @@ export default function DesaSetting() { let finalData = { ...dataEdit }; // ← buffer data terbaru if (dataEdit.name === "TTD") { - const resImg = await apiFetch.api.pengaduan.upload.post({ file: img }); + const oldImg = await apiFetch.api.pengaduan["delete-image"].post({ file: dataEdit.value, folder: "lainnya" }); + const resImg = await apiFetch.api.pengaduan.upload.post({ file: img, folder: "lainnya" }); if (resImg.status === 200) { finalData = { @@ -176,7 +177,7 @@ export default function DesaSetting() { setOpenedPreview(false)} - folder="syarat-dokumen" + folder="lainnya" fileName={viewImg} /> diff --git a/src/components/ModalFile.tsx b/src/components/ModalFile.tsx index f770ad0..71766c8 100644 --- a/src/components/ModalFile.tsx +++ b/src/components/ModalFile.tsx @@ -1,10 +1,12 @@ +import { detectFileType } from "@/server/lib/detect-type-of-file"; import { Flex, Image, Loader, Modal } from "@mantine/core"; import { useEffect, useState } from "react"; import notification from "./notificationGlobal"; export default function ModalFile({ open, onClose, folder, fileName }: { open: boolean, onClose: () => void, folder: string, fileName: string }) { - const [viewImg, setViewImg] = useState(""); + const [viewFile, setViewFile] = useState(""); const [loading, setLoading] = useState(false); + const [typeFile, setTypeFile] = useState(""); useEffect(() => { if (open && fileName) { @@ -12,12 +14,18 @@ export default function ModalFile({ open, onClose, folder, fileName }: { open: b } }, [open, fileName]); + const loadImage = async () => { try { - setViewImg(""); + setViewFile(""); setLoading(true); + + // detect type of file + const { ext, type } = detectFileType(fileName); + setTypeFile(type || ""); + + // load file const urlApi = '/api/pengaduan/image?folder=' + folder + '&fileName=' + fileName; - // Fetch manual agar mendapatkan Response asli const res = await fetch(urlApi); if (!res.ok) return notification({ @@ -28,7 +36,7 @@ export default function ModalFile({ open, onClose, folder, fileName }: { open: b const blob = await res.blob(); const url = URL.createObjectURL(blob); - setViewImg(url); + setViewFile(url); } catch (err) { console.error("Gagal load gambar:", err); } finally { @@ -43,7 +51,7 @@ export default function ModalFile({ open, onClose, folder, fileName }: { open: b opened={open} onClose={onClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} - size="lg" + size="xl" withCloseButton removeScrollProps={{ allowPinchZoom: true }} title="File" @@ -53,13 +61,19 @@ export default function ModalFile({ open, onClose, folder, fileName }: { open: b )} - {viewImg && ( - + {viewFile && ( + <> + {typeFile == "pdf" ? ( + + ) : ( + + )} + )} ); diff --git a/src/components/ModalSurat.tsx b/src/components/ModalSurat.tsx index 1ca5374..cd07704 100644 --- a/src/components/ModalSurat.tsx +++ b/src/components/ModalSurat.tsx @@ -44,7 +44,6 @@ export default function ModalSurat({ open, onClose, surat }: { open: boolean, on const downloadPDF = async () => { const element = hiddenRef.current; - const canvas = await html2canvas(element, { scale: 2, useCORS: true, @@ -64,7 +63,7 @@ export default function ModalSurat({ open, onClose, surat }: { open: boolean, on pdf.addImage(imgData, "JPEG", 0, 0, imgWidth, imgHeight); - pdf.save("surat-keterangan-usaha.pdf"); + pdf.save(`${data?.data?.surat?.nameCategory}.pdf`); }; return ( diff --git a/src/components/surat/SKBedaBiodataDiri.tsx b/src/components/surat/SKBedaBiodataDiri.tsx index 2c0278d..9279246 100644 --- a/src/components/surat/SKBedaBiodataDiri.tsx +++ b/src/components/surat/SKBedaBiodataDiri.tsx @@ -1,11 +1,42 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKBedaBiodataDiri({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + + return (
{/* HEADER */} @@ -113,13 +144,12 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
{/* TANDA TANGAN */} -
+
- -

Kepala Desa / Lurah {data.setting.desaNama} -



- {data.setting.perbekelNama}
+

+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKBelumKawin.tsx b/src/components/surat/SKBelumKawin.tsx index add1172..46f0f23 100644 --- a/src/components/surat/SKBelumKawin.tsx +++ b/src/components/surat/SKBelumKawin.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKBelumKawin({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
{/* HEADER */} @@ -62,13 +92,14 @@ export default function SKBelumKawin({ data }: { data: any }) {


Pemohon -



+





{getValue("nama")}


Kepala Desa / Lurah {data.setting.desaNama} -



+

+ ttd perbekel
{data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKDomisiliOrganisasi.tsx b/src/components/surat/SKDomisiliOrganisasi.tsx index e4994ea..769d70a 100644 --- a/src/components/surat/SKDomisiliOrganisasi.tsx +++ b/src/components/surat/SKDomisiliOrganisasi.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKDomisiliOrganisasi({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
{/* HEADER */} @@ -79,11 +109,11 @@ export default function SKDomisiliOrganisasi({ data }: { data: any }) { {/* TANDA TANGAN */}
- -

+
Kepala Desa / Lurah {data.setting.desaNama} -



- {data.setting.perbekelNama}
+

+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKKelahiran.tsx b/src/components/surat/SKKelahiran.tsx index 08b58d3..9d7722b 100644 --- a/src/components/surat/SKKelahiran.tsx +++ b/src/components/surat/SKKelahiran.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKKelahiran({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
@@ -102,8 +132,9 @@ export default function SKKelahiran({ data }: { data: any }) {
Kepala Desa / Lurah {data.setting.desaNama} -



- {data.setting.perbekelNama}
+
+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKKelakuanBaik.tsx b/src/components/surat/SKKelakuanBaik.tsx index 876e55c..fdb862d 100644 --- a/src/components/surat/SKKelakuanBaik.tsx +++ b/src/components/surat/SKKelakuanBaik.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKKelakuanBaik({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
@@ -103,8 +133,9 @@ export default function SKKelakuanBaik({ data }: { data: any }) {
Kepala Desa {data.setting.desaNama} -



- {data.setting.perbekelNama}
+

+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKKematian.tsx b/src/components/surat/SKKematian.tsx index eb5e37c..417544b 100644 --- a/src/components/surat/SKKematian.tsx +++ b/src/components/surat/SKKematian.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKKematian({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
{/* HEADER */} @@ -72,13 +102,14 @@ export default function SKKematian({ data }: { data: any }) {


Pemohon -



+




{getValue("nama")}
-

+
Kepala Desa / Lurah {data.setting.desaNama} -



+

+ ttd perbekel
{data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKPenghasilan.tsx b/src/components/surat/SKPenghasilan.tsx index d42eb6c..0b920f2 100644 --- a/src/components/surat/SKPenghasilan.tsx +++ b/src/components/surat/SKPenghasilan.tsx @@ -1,11 +1,41 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKPenghasilan({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (jenis: string) => _.upperFirst( data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
{/* HEADER */} @@ -102,8 +132,9 @@ export default function SKPenghasilan({ data }: { data: any }) {
Kepala Desa / Lurah {data.setting.desaNama} -



- {data.setting.perbekelNama}
+

+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/components/surat/SKTempatUsaha.tsx b/src/components/surat/SKTempatUsaha.tsx index efae86f..96d4b28 100644 --- a/src/components/surat/SKTempatUsaha.tsx +++ b/src/components/surat/SKTempatUsaha.tsx @@ -1,9 +1,40 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKTempatUsaha({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (key: string) => _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + + return (
{/* TITLE */} @@ -68,8 +99,8 @@ export default function SKTempatUsaha({ data }: { data: any }) { {/* TANDA TANGAN */}
- {data.setting.desaNama}, {data.surat.createdAt}


- + {data.setting.desaKabupaten}, {data.surat.createdAt}

+ ttd perbekel
{data.setting.perbekelNama}
{data.setting.perbekelJabatan + " " + data.setting.desaNama}
diff --git a/src/components/surat/SKTidakMampu.tsx b/src/components/surat/SKTidakMampu.tsx index e0b62ba..f483e6c 100644 --- a/src/components/surat/SKTidakMampu.tsx +++ b/src/components/surat/SKTidakMampu.tsx @@ -1,9 +1,40 @@ import _ from "lodash"; +import { useEffect, useState } from "react"; +import notification from "../notificationGlobal"; export default function SKTidakMampu({ data }: { data: any }) { + const [viewImg, setViewImg] = useState(""); const getValue = (key: string) => _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useEffect(() => { + loadImage(); + }, [data]); + return (
{/* TITLE */} @@ -59,8 +90,8 @@ export default function SKTidakMampu({ data }: { data: any }) { {/* TANDA TANGAN */}
- {data.setting.desaNama}, {data.surat.createdAt}


- + {data.setting.desaKabupaten}, {data.surat.createdAt}

+ ttd perbekel
{data.setting.perbekelNama}
{data.setting.perbekelJabatan + " " + data.setting.desaNama}
diff --git a/src/components/surat/SKUsaha.tsx b/src/components/surat/SKUsaha.tsx index f1f6738..470d188 100644 --- a/src/components/surat/SKUsaha.tsx +++ b/src/components/surat/SKUsaha.tsx @@ -14,7 +14,7 @@ export default function SKUsaha({ data }: { data: any }) { setViewImg(""); if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=syarat-dokumen&fileName=' + data.setting.perbekelTTD; + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; // Fetch manual agar mendapatkan Response asli const res = await fetch(urlApi); if (!res.ok) @@ -132,13 +132,12 @@ export default function SKUsaha({ data }: { data: any }) {
{/* TANDA TANGAN */} -
+
- -

+
Kepala Desa / Lurah {data.setting.desaNama}

- ttd perbekel + ttd perbekel
{data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP} diff --git a/src/components/surat/SKYatimPiatu.tsx b/src/components/surat/SKYatimPiatu.tsx index c80763b..e8c18f8 100644 --- a/src/components/surat/SKYatimPiatu.tsx +++ b/src/components/surat/SKYatimPiatu.tsx @@ -1,10 +1,40 @@ +import { useShallowEffect } from "@mantine/hooks"; import _ from "lodash"; +import { useState } from "react"; +import notification from "../notificationGlobal"; export default function SKYatim({ data }: { data: any }) { - + const [viewImg, setViewImg] = useState(""); const getValue = (key: string) => _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; + + const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; + + useShallowEffect(() => { + loadImage(); + }, [data]); + return (
@@ -146,14 +176,15 @@ export default function SKYatim({ data }: { data: any }) { -

+
{/* TTD */}
Kepala Desa {data.setting.desaNama} -



- {data.setting.perbekelNama}
+

+ ttd perbekel
+ {data.setting.perbekelNama}
NIP. {data.setting.perbekelNIP}
diff --git a/src/server/lib/detect-type-of-file.ts b/src/server/lib/detect-type-of-file.ts new file mode 100644 index 0000000..96c721e --- /dev/null +++ b/src/server/lib/detect-type-of-file.ts @@ -0,0 +1,25 @@ +function getExtension(fileName: string): string | null { + if (!fileName || typeof fileName !== "string") return null; + + const parts = fileName.split("."); + if (parts.length <= 1) return null; + + return parts.pop()?.toLowerCase() || null; +} + + +export function detectFileType(fileName: string) { + const ext = getExtension(fileName); + + if (!ext) return { ext: null, type: "unknown" }; + + if (["jpg", "jpeg", "png", "gif", "webp", "bmp"].includes(ext)) { + return { ext, type: "image" }; + } + + if (ext === "pdf") { + return { ext, type: "pdf" }; + } + + return { ext, type: "other" }; +} diff --git a/src/server/lib/seafile.ts b/src/server/lib/seafile.ts index 8c137e5..4343e13 100644 --- a/src/server/lib/seafile.ts +++ b/src/server/lib/seafile.ts @@ -94,7 +94,6 @@ export async function fetchWithAuth(config: Config, url: string, options: Reques } catch { console.error('🔍 Could not read response body'); } - process.exit(1); } return response; } @@ -139,7 +138,7 @@ export async function catFile(config: Config, folder: string, fileName: string): return buffer; } -export async function uploadFile(config: Config, file: File): Promise { +export async function uploadFile(config: Config, file: File, folder: string): Promise { const remoteName = path.basename(file.name); // 1. Dapatkan upload link (pakai Authorization) @@ -152,7 +151,7 @@ export async function uploadFile(config: Config, file: File): Promise { // 2. Siapkan form-data const formData = new FormData(); formData.append("parent_dir", "/"); - formData.append("relative_path", "syarat-dokumen"); // tanpa slash di akhir + formData.append("relative_path", folder); // tanpa slash di akhir formData.append("file", file, remoteName); // file langsung, jangan pakai Blob // 3. Upload file TANPA Authorization header, token di query param @@ -232,10 +231,10 @@ export async function uploadFileToFolder(config: Config, base64File: { name: str } +export async function removeFile(config: Config, fileName: string, folder: string): Promise { + const res = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${folder}/${fileName}`, { method: 'DELETE' }); - -export async function removeFile(config: Config, fileName: string): Promise { - await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${fileName}`, { method: 'DELETE' }); + if (!res.ok) return 'gagal menghapus file'; return `🗑️ Removed ${fileName}` } diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts index ad0bab4..9fa4c4f 100644 --- a/src/server/routes/pengaduan_route.ts +++ b/src/server/routes/pengaduan_route.ts @@ -7,7 +7,7 @@ import { generateNoPengaduan } from "../lib/no-pengaduan" import { normalizePhoneNumber } from "../lib/normalizePhone" import { prisma } from "../lib/prisma" import { renameFile } from "../lib/rename-file" -import { catFile, defaultConfigSF, uploadFile, uploadFileBase64 } from "../lib/seafile" +import { catFile, defaultConfigSF, removeFile, uploadFile, uploadFileBase64 } from "../lib/seafile" const PengaduanRoute = new Elysia({ prefix: "pengaduan", @@ -176,7 +176,7 @@ const PengaduanRoute = new Elysia({ title: judulPengaduan, detail: detailPengaduan, idCategory: idCategoryFix, - idWarga: idWargaFix, + idWarga: idWargaFix || "", location: lokasi, image: imageFix, noPengaduan, @@ -218,23 +218,20 @@ const PengaduanRoute = new Elysia({ description: "Alamat atau titik lokasi pengaduan" }), - namaGambar: t.String({ - optional: true, + namaGambar: t.Optional(t.String({ examples: ["sampah.jpg"], description: "Nama file gambar yang telah diupload (opsional)" - }), + })), - kategoriId: t.String({ - optional: true, + kategoriId: t.Optional(t.String({ examples: ["kebersihan"], description: "ID atau nama kategori pengaduan (contoh: kebersihan, keamanan, lainnya)" - }), + })), - wargaId: t.String({ - optional: true, + wargaId: t.Optional(t.String({ examples: ["budiman"], description: "ID unik warga yang melapor (jika sudah terdaftar)" - }), + })), noTelepon: t.String({ error: "Nomor telepon harus diisi", @@ -517,7 +514,7 @@ Respon: } }) .post("/upload", async ({ body }) => { - const { file } = body; + const { file, folder } = body; // Validasi file if (!file) { @@ -530,7 +527,7 @@ Respon: // Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer) // const buffer = await file.arrayBuffer(); - const result = await uploadFile(defaultConfigSF, renamedFile); + const result = await uploadFile(defaultConfigSF, renamedFile, folder); if (result == 'gagal') { return { success: false, message: "Upload gagal" }; } @@ -544,7 +541,8 @@ Respon: }; }, { body: t.Object({ - file: t.Any() + file: t.Any(), + folder: t.String(), }), detail: { summary: "Upload File", @@ -728,12 +726,14 @@ Respon: const hasil = await catFile(defaultConfigSF, folder, fileName); const ext = fileName.split(".").pop()?.toLowerCase(); - const mime = - ext === "jpg" || ext === "jpeg" - ? "image/jpeg" - : ext === "png" - ? "image/png" - : "application/octet-stream"; + let mime = "application/octet-stream"; // default + + if (["jpg", "jpeg"].includes(ext!)) mime = "image/jpeg"; + if (["png"].includes(ext!)) mime = "image/png"; + if (["gif"].includes(ext!)) mime = "image/gif"; + if (["webp"].includes(ext!)) mime = "image/webp"; + if (["svg"].includes(ext!)) mime = "image/svg+xml"; + if (["pdf"].includes(ext!)) mime = "application/pdf"; set.headers["Content-Type"] = mime; set.headers["Content-Length"] = hasil.byteLength.toString(); @@ -749,6 +749,33 @@ Respon: description: "tool untuk mendapatkan gambar", } }) + .post("/delete-image", async ({ body }) => { + const { file, folder } = body; + + // Validasi file + if (!file) { + return { success: false, message: "File tidak ditemukan" }; + } + + const result = await removeFile(defaultConfigSF, file, folder); + if (result == 'gagal') { + return { success: false, message: "Delete gagal" }; + } + + return { + success: true, + message: "Delete berhasil", + }; + }, { + body: t.Object({ + file: t.String(), + folder: t.String(), + }), + detail: { + summary: "Delete File", + description: "Tool untuk delete file Seafile", + }, + }) ; diff --git a/src/server/routes/surat_route.ts b/src/server/routes/surat_route.ts index 21eacb3..c688522 100644 --- a/src/server/routes/surat_route.ts +++ b/src/server/routes/surat_route.ts @@ -21,6 +21,11 @@ const SuratRoute = new Elysia({ select: { DataTextPelayanan: true, } + }, + CategoryPelayanan: { + select: { + name: true, + } } } }) @@ -37,6 +42,7 @@ const SuratRoute = new Elysia({ surat: { id: dataSurat?.id, idCategory: dataSurat?.idCategory, + nameCategory: dataSurat?.CategoryPelayanan?.name, noSurat: dataSurat?.noSurat, dataText: dataSurat?.PelayananAjuan?.DataTextPelayanan, createdAt: dataSurat?.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }), diff --git a/src/server/routes/warga_route.ts b/src/server/routes/warga_route.ts index d5324ed..507b143 100644 --- a/src/server/routes/warga_route.ts +++ b/src/server/routes/warga_route.ts @@ -62,8 +62,8 @@ const WargaRoute = new Elysia({ phone: t.String({ minLength: 1 }) }), detail: { - summary: "edit konfigurasi desa", - description: `tool untuk edit konfigurasi desa` + summary: "Edit Warga", + description: `tool untuk edit warga` } }) .get("/detail", async ({ query }) => {