upd: dashboard admin
Deskripsi - edit upload ttd setting desa No Issues
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import apiFetch from "@/lib/apiFetch";
|
import apiFetch from "@/lib/apiFetch";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Anchor,
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
|
FileInput,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
Input,
|
Input,
|
||||||
@@ -10,7 +12,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||||
import { IconEdit } from "@tabler/icons-react";
|
import { IconEdit } from "@tabler/icons-react";
|
||||||
@@ -22,6 +24,7 @@ export default function DesaSetting() {
|
|||||||
const [btnDisable, setBtnDisable] = useState(false);
|
const [btnDisable, setBtnDisable] = useState(false);
|
||||||
const [btnLoading, setBtnLoading] = useState(false);
|
const [btnLoading, setBtnLoading] = useState(false);
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const [img, setImg] = useState<any>()
|
||||||
const { data, mutate, isLoading } = useSWR("/", () =>
|
const { data, mutate, isLoading } = useSWR("/", () =>
|
||||||
apiFetch.api["configuration-desa"].list.get(),
|
apiFetch.api["configuration-desa"].list.get(),
|
||||||
);
|
);
|
||||||
@@ -39,7 +42,31 @@ export default function DesaSetting() {
|
|||||||
async function handleEdit() {
|
async function handleEdit() {
|
||||||
try {
|
try {
|
||||||
setBtnLoading(true);
|
setBtnLoading(true);
|
||||||
const res = await apiFetch.api["configuration-desa"].edit.post(dataEdit);
|
|
||||||
|
let finalData = { ...dataEdit }; // ← buffer data terbaru
|
||||||
|
|
||||||
|
if (dataEdit.name === "TTD") {
|
||||||
|
const resImg = await apiFetch.api.pengaduan.upload.post({ file: img });
|
||||||
|
|
||||||
|
if (resImg.status === 200) {
|
||||||
|
finalData = {
|
||||||
|
...finalData,
|
||||||
|
value: resImg.data?.filename || ""
|
||||||
|
};
|
||||||
|
|
||||||
|
setDataEdit(finalData); // update state
|
||||||
|
} else {
|
||||||
|
return notification({
|
||||||
|
title: "Error",
|
||||||
|
message: "Failed to upload image",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const res = await apiFetch.api["configuration-desa"].edit.post(finalData);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
mutate();
|
mutate();
|
||||||
close();
|
close();
|
||||||
@@ -67,6 +94,7 @@ export default function DesaSetting() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function chooseEdit({
|
function chooseEdit({
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
@@ -100,18 +128,35 @@ export default function DesaSetting() {
|
|||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={"Edit"}
|
title={"Edit"}
|
||||||
centered
|
|
||||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||||
>
|
>
|
||||||
<Stack gap="ld">
|
<Stack gap="ld">
|
||||||
<Input.Wrapper label={dataEdit.name}>
|
{
|
||||||
<Input
|
dataEdit.name == "TTD"
|
||||||
value={dataEdit.value}
|
?
|
||||||
onChange={(e) =>
|
(
|
||||||
onValidation({ kat: "value", value: e.target.value })
|
<Input.Wrapper label={dataEdit.name}>
|
||||||
}
|
<FileInput
|
||||||
/>
|
clearable
|
||||||
</Input.Wrapper>
|
placeholder="Upload TTD"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => { setImg(e) }}
|
||||||
|
/>
|
||||||
|
</Input.Wrapper>
|
||||||
|
)
|
||||||
|
:
|
||||||
|
(
|
||||||
|
<Input.Wrapper label={dataEdit.name}>
|
||||||
|
<Input
|
||||||
|
value={dataEdit.value}
|
||||||
|
onChange={(e) =>
|
||||||
|
onValidation({ kat: "value", value: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Input.Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<Group justify="center" grow>
|
<Group justify="center" grow>
|
||||||
<Button variant="light" onClick={close}>
|
<Button variant="light" onClick={close}>
|
||||||
Batal
|
Batal
|
||||||
@@ -119,7 +164,7 @@ export default function DesaSetting() {
|
|||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
disabled={btnDisable}
|
disabled={btnDisable || (dataEdit.name == "TTD" && !img)}
|
||||||
loading={btnLoading}
|
loading={btnLoading}
|
||||||
>
|
>
|
||||||
Simpan
|
Simpan
|
||||||
@@ -127,6 +172,8 @@ export default function DesaSetting() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|
||||||
<Stack gap={"md"}>
|
<Stack gap={"md"}>
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
<Title order={4} c="gray.2">
|
<Title order={4} c="gray.2">
|
||||||
@@ -147,7 +194,17 @@ export default function DesaSetting() {
|
|||||||
{list?.map((v: any) => (
|
{list?.map((v: any) => (
|
||||||
<Table.Tr key={v.id}>
|
<Table.Tr key={v.id}>
|
||||||
<Table.Td>{v.name}</Table.Td>
|
<Table.Td>{v.name}</Table.Td>
|
||||||
<Table.Td>{v.value}</Table.Td>
|
<Table.Td>
|
||||||
|
{
|
||||||
|
v.name == "TTD"
|
||||||
|
?
|
||||||
|
<Anchor href="https://mantine.dev/" target="_blank" underline="always">
|
||||||
|
Lihat
|
||||||
|
</Anchor>
|
||||||
|
:
|
||||||
|
v.value
|
||||||
|
}
|
||||||
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label="Edit Setting">
|
<Tooltip label="Edit Setting">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
|
|||||||
@@ -4,16 +4,10 @@ import KategoriPengaduan from "@/components/KategoriPengaduan";
|
|||||||
import ProfileUser from "@/components/ProfileUser";
|
import ProfileUser from "@/components/ProfileUser";
|
||||||
import UserSetting from "@/components/UserSetting";
|
import UserSetting from "@/components/UserSetting";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Card,
|
Card,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
|
||||||
Flex,
|
|
||||||
Grid,
|
Grid,
|
||||||
NavLink,
|
NavLink
|
||||||
Stack,
|
|
||||||
Table,
|
|
||||||
Title,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconBuildingBank,
|
IconBuildingBank,
|
||||||
|
|||||||
12
src/server/lib/rename-file.ts
Normal file
12
src/server/lib/rename-file.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { mimeToExtension } from "./mimetypeToExtension";
|
||||||
|
|
||||||
|
export function renameFile({ oldFile, newName }: { oldFile: File; newName: string }) {
|
||||||
|
const ext = mimeToExtension(oldFile.type)
|
||||||
|
const nameFix = newName == 'random' ? `${uuidv4()}.${ext}` : newName
|
||||||
|
|
||||||
|
return new File([oldFile], nameFix, {
|
||||||
|
type: oldFile.type,
|
||||||
|
lastModified: oldFile.lastModified,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ export async function uploadFile(config: Config, file: File): Promise<string> {
|
|||||||
|
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`Upload failed: ${text}`);
|
if (!res.ok) return 'gagal'
|
||||||
return `✅ Uploaded ${file.name} successfully`;
|
return `✅ Uploaded ${file.name} successfully`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { mimeToExtension } from "../lib/mimetypeToExtension"
|
|||||||
import { generateNoPengaduan } from "../lib/no-pengaduan"
|
import { generateNoPengaduan } from "../lib/no-pengaduan"
|
||||||
import { normalizePhoneNumber } from "../lib/normalizePhone"
|
import { normalizePhoneNumber } from "../lib/normalizePhone"
|
||||||
import { prisma } from "../lib/prisma"
|
import { prisma } from "../lib/prisma"
|
||||||
|
import { renameFile } from "../lib/rename-file"
|
||||||
import { catFile, defaultConfigSF, testConnection, uploadFile, uploadFileBase64 } from "../lib/seafile"
|
import { catFile, defaultConfigSF, testConnection, uploadFile, uploadFileBase64 } from "../lib/seafile"
|
||||||
|
|
||||||
const PengaduanRoute = new Elysia({
|
const PengaduanRoute = new Elysia({
|
||||||
@@ -523,20 +524,27 @@ Respon:
|
|||||||
return { success: false, message: "File tidak ditemukan" };
|
return { success: false, message: "File tidak ditemukan" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename file
|
||||||
|
const renamedFile = renameFile({ oldFile: file, newName: 'random' });
|
||||||
|
|
||||||
|
|
||||||
// Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer)
|
// Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer)
|
||||||
// const buffer = await file.arrayBuffer();
|
// const buffer = await file.arrayBuffer();
|
||||||
const result = await uploadFile(defaultConfigSF, file);
|
const result = await uploadFile(defaultConfigSF, renamedFile);
|
||||||
|
if (result == 'gagal') {
|
||||||
|
return { success: false, message: "Upload gagal" };
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Upload berhasil",
|
message: "Upload berhasil",
|
||||||
filename: file.name,
|
filename: renamedFile.name,
|
||||||
size: file.size,
|
size: renamedFile.size,
|
||||||
seafileResult: result
|
seafileResult: result
|
||||||
};
|
};
|
||||||
}, {
|
}, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
file: t.File({ format: "binary" })
|
file: t.Any()
|
||||||
}),
|
}),
|
||||||
detail: {
|
detail: {
|
||||||
summary: "Upload File",
|
summary: "Upload File",
|
||||||
|
|||||||
Reference in New Issue
Block a user