UI Desa Gallery Done
API Desa Gallery Done
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
"@mantine/carousel": "^7.16.2",
|
||||
"@mantine/charts": "^7.17.1",
|
||||
"@mantine/core": "^7.17.4",
|
||||
"@mantine/dates": "^7.17.4",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"@mantine/dropzone": "^7.17.0",
|
||||
"@mantine/form": "^8.1.0",
|
||||
"@mantine/hooks": "^7.17.4",
|
||||
|
||||
@@ -50,36 +50,39 @@ model AppMenuChild {
|
||||
// ========================================= FILE STORAGE ========================================= //
|
||||
|
||||
model FileStorage {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
ProfilePPID ProfilePPID[]
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
ProfilePPID ProfilePPID[]
|
||||
StrukturPPID StrukturPPID[]
|
||||
|
||||
GalleryFoto GalleryFoto[]
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
|
||||
//========================================= STRUKTUR PPID ========================================= //
|
||||
model StrukturPPID {
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= VISI MISI PPID ========================================= //
|
||||
model VisiMisiPPID {
|
||||
id String @id @default(cuid())
|
||||
@@ -104,18 +107,18 @@ model DasarHukumPPID {
|
||||
|
||||
// ========================================= PROFILE PPID ========================================= //
|
||||
model ProfilePPID {
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
biodata String @db.Text
|
||||
riwayat String @db.Text
|
||||
pengalaman String @db.Text
|
||||
unggulan String @db.Text
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
biodata String @db.Text
|
||||
riwayat String @db.Text
|
||||
pengalaman String @db.Text
|
||||
unggulan String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
|
||||
@@ -334,51 +337,28 @@ model CategoryPengumuman {
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= IMAGES ========================================= //
|
||||
model Images {
|
||||
id String @id @default(cuid())
|
||||
url String
|
||||
label String @default("null")
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
GalleryFoto GalleryFoto[]
|
||||
}
|
||||
|
||||
// ========================================= VIDEOS ========================================= //
|
||||
model Videos {
|
||||
id String @id @default(cuid())
|
||||
url String
|
||||
label String @default("null")
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
GalleryVideo GalleryVideo[]
|
||||
}
|
||||
|
||||
// ========================================= GALLERY ========================================= //
|
||||
model GalleryFoto {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
image String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
imagesId String? @unique
|
||||
imageGalleryFoto Images? @relation(fields: [imagesId], references: [id])
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
imagesId String? @unique
|
||||
imageGalleryFoto FileStorage? @relation(fields: [imagesId], references: [id])
|
||||
}
|
||||
|
||||
model GalleryVideo {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
video String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
videosId String? @unique
|
||||
videosGalleryVideo Videos? @relation(fields: [videosId], references: [id])
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
deskripsi String @db.Text
|
||||
linkVideo String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= MENU KESEHATAN ========================================= //
|
||||
|
||||
BIN
public/Share.png
Normal file
BIN
public/Share.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
public/bagikanPostingan.png
Normal file
BIN
public/bagikanPostingan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
BIN
public/sematkan.png
Normal file
BIN
public/sematkan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
BIN
public/video.png
Normal file
BIN
public/video.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
415
src/app/admin/(dashboard)/_state/desa/gallery.ts
Normal file
415
src/app/admin/(dashboard)/_state/desa/gallery.ts
Normal file
@@ -0,0 +1,415 @@
|
||||
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 fotoForm = z.object({
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||
imagesId: z.string().nonempty(),
|
||||
});
|
||||
|
||||
const videoForm = z.object({
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||
linkVideo: z.string().min(1, { message: "Link video is required" }),
|
||||
});
|
||||
|
||||
const defaultFormFoto = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
imagesId: "",
|
||||
};
|
||||
|
||||
const defaultFormVideo = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
linkVideo: "",
|
||||
};
|
||||
|
||||
const foto = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormFoto },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = fotoForm.safeParse(foto.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
foto.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.gallery.foto["create"].post(
|
||||
foto.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
foto.findMany.load();
|
||||
return toast.success("Foto berhasil disimpan!");
|
||||
}
|
||||
return toast.error("Gagal menyimpan foto");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
foto.create.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
foto.create.form = { ...defaultFormFoto };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GalleryFotoGetPayload<{
|
||||
include: {
|
||||
imageGalleryFoto: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
foto.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GalleryFotoGetPayload<{
|
||||
include: {
|
||||
imageGalleryFoto: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/gallery/foto/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
foto.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch foto:", res.statusText);
|
||||
foto.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching foto:", error);
|
||||
foto.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
foto.delete.loading = true;
|
||||
const response = await fetch(`/api/desa/gallery/foto/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Foto berhasil dihapus");
|
||||
await foto.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result.message || "Gagal menghapus foto");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus foto");
|
||||
} finally {
|
||||
foto.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormFoto },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/desa/gallery/foto/${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 = {
|
||||
name: data.name,
|
||||
deskripsi: data.deskripsi,
|
||||
imagesId: data.imagesId || "",
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading foto:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = fotoForm.safeParse(foto.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
foto.update.loading = true;
|
||||
const response = await fetch(`/api/desa/gallery/foto/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
deskripsi: this.form.deskripsi,
|
||||
imagesId: this.form.imagesId,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Foto berhasil diupdate");
|
||||
await foto.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate foto");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating foto:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengupdate foto"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
foto.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
foto.update.id = "";
|
||||
foto.update.form = { ...defaultFormFoto };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const video = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormVideo },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = videoForm.safeParse(video.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
video.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.gallery.video["create"].post(
|
||||
video.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
video.findMany.load();
|
||||
return toast.success("Video berhasil disimpan!");
|
||||
}
|
||||
return toast.error("Gagal menyimpan video");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
video.create.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
video.create.form = { ...defaultFormVideo };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GalleryVideoGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
video.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GalleryVideoGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/gallery/video/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
video.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch video:", res.statusText);
|
||||
video.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching video:", error);
|
||||
video.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
video.delete.loading = true;
|
||||
const response = await fetch(`/api/desa/gallery/video/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Video berhasil dihapus");
|
||||
await video.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus video");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus video");
|
||||
} finally {
|
||||
video.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormVideo },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/desa/gallery/video/${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 = {
|
||||
name: data.name,
|
||||
deskripsi: data.deskripsi,
|
||||
linkVideo: data.linkVideo,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading video:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = videoForm.safeParse(video.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
video.update.loading = true;
|
||||
const response = await fetch(`/api/desa/gallery/video/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
deskripsi: this.form.deskripsi,
|
||||
linkVideo: this.form.linkVideo,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Video berhasil diupdate");
|
||||
await video.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate video");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating video:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengupdate video"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
video.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
video.update.id = "";
|
||||
video.update.form = { ...defaultFormVideo };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateGallery = proxy({
|
||||
foto,
|
||||
video,
|
||||
});
|
||||
|
||||
export default stateGallery;
|
||||
@@ -52,7 +52,10 @@ const grafikBerdasarkanResponden = proxy({
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
grafikBerdasarkanResponden.create.form = {
|
||||
...defaultForm
|
||||
sangatbaik: "",
|
||||
baik: "",
|
||||
kurangbaik: "",
|
||||
tidakbaik: "",
|
||||
};
|
||||
grafikBerdasarkanResponden.findMany.load();
|
||||
return id;
|
||||
@@ -109,7 +112,7 @@ const grafikBerdasarkanResponden = proxy({
|
||||
form: {...defaultForm},
|
||||
loading: false,
|
||||
async byId() {
|
||||
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
|
||||
119
src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx
Normal file
119
src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconImageInPicture } 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 EditFoto() {
|
||||
const fotoState = useProxy(stateGallery.foto)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFoto = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
try {
|
||||
const data = await fotoState.update.load(id);
|
||||
if (data) {
|
||||
if (data?.imageGalleryFoto?.link) {
|
||||
setPreviewImage(data.imageGalleryFoto.link);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading foto:', error);
|
||||
toast.error('Gagal memuat data foto');
|
||||
}
|
||||
};
|
||||
loadFoto();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (file) {
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file,
|
||||
name: file.name,
|
||||
});
|
||||
const uploaded = res.data?.data;
|
||||
if (!uploaded?.id) {
|
||||
return toast.error("Gagal upload gambar");
|
||||
}
|
||||
fotoState.update.form.imagesId = uploaded.id;
|
||||
}
|
||||
await fotoState.update.update();
|
||||
toast.success('Foto berhasil diperbarui!');
|
||||
router.push('/admin/desa/gallery/foto');
|
||||
} catch (error) {
|
||||
console.error('Error updating foto:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui foto');
|
||||
}
|
||||
};
|
||||
|
||||
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 Foto</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||
placeholder='Masukkan judul foto'
|
||||
value={fotoState.update.form.name}
|
||||
onChange={(e) =>
|
||||
(fotoState.update.form.name = e.target.value)
|
||||
}
|
||||
/>
|
||||
<FileInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||
value={file}
|
||||
onChange={async (e) => {
|
||||
if (!e) return;
|
||||
setFile(e);
|
||||
const base64 = await e.arrayBuffer().then((buf) =>
|
||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||
);
|
||||
setPreviewImage(base64);
|
||||
}}
|
||||
/>
|
||||
{previewImage ? (
|
||||
<Image alt="" src={previewImage} w={200} h={200} />
|
||||
) : (
|
||||
<Center w={200} h={200} bg={"gray"}>
|
||||
<IconImageInPicture />
|
||||
</Center>
|
||||
)}
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||
<EditEditor
|
||||
value={fotoState.update.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
fotoState.update.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditFoto;
|
||||
112
src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx
Normal file
112
src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
'use client'
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useState } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import colors from '@/con/colors';
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailFoto() {
|
||||
const fotoState = useProxy(stateGallery.foto)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
fotoState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
fotoState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/desa/gallery/foto")
|
||||
}
|
||||
}
|
||||
|
||||
if (!fotoState.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 Foto</Text>
|
||||
{fotoState.findUnique.data ? (
|
||||
<Paper key={fotoState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||
<Text fz={"lg"}>{fotoState.findUnique.data?.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tanggal Foto</Text>
|
||||
<Text fz={"lg"}>{new Date(fotoState.findUnique.data?.createdAt).toDateString()}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: fotoState.findUnique.data?.deskripsi }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||
<Image w={{ base: 300, md: 350}} src={fotoState.findUnique.data?.imageGalleryFoto?.link} alt="gambar" />
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (fotoState.findUnique.data) {
|
||||
setSelectedId(fotoState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={fotoState.delete.loading || !fotoState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (fotoState.findUnique.data) {
|
||||
router.push(`/admin/desa/gallery/foto/${fotoState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!fotoState.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 berita ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailFoto;
|
||||
@@ -1,45 +1,108 @@
|
||||
'use client'
|
||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateFoto() {
|
||||
const fotoState = useProxy(stateGallery.foto)
|
||||
const router = useRouter();
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
const resetForm = () => {
|
||||
fotoState.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
imagesId: "",
|
||||
};
|
||||
|
||||
setPreviewImage(null)
|
||||
setFile(null)
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!file) {
|
||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||
}
|
||||
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file,
|
||||
name: file.name,
|
||||
});
|
||||
|
||||
const uploaded = res.data?.data;
|
||||
if (!uploaded?.id) {
|
||||
return toast.error("Gagal upload gambar");
|
||||
}
|
||||
|
||||
fotoState.create.form.imagesId = uploaded.id;
|
||||
await fotoState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/gallery/foto")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Foto</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||
placeholder='Masukkan judul foto'
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||
placeholder='Masukkan judul foto'
|
||||
value={fotoState.create.form.name}
|
||||
onChange={(val) => {
|
||||
fotoState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
|
||||
placeholder='Masukkan tanggal foto'
|
||||
<FileInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||
value={file}
|
||||
onChange={async (e) => {
|
||||
if (!e) return;
|
||||
setFile(e);
|
||||
const base64 = await e.arrayBuffer().then((buf) =>
|
||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||
);
|
||||
setPreviewImage(base64);
|
||||
}}
|
||||
/>
|
||||
{previewImage ? (
|
||||
<Image alt="" src={previewImage} w={200} h={200} />
|
||||
) : (
|
||||
<Center w={200} h={200} bg={"gray"}>
|
||||
<IconImageInPicture />
|
||||
</Center>
|
||||
)}
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||
<KeamananEditor
|
||||
showSubmit={false}
|
||||
<CreateEditor
|
||||
value={fotoState.create.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
fotoState.create.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailFoto() {
|
||||
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 Foto</Text>
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Judul Foto</Text>
|
||||
<Text>Foto 1</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Tanggal Foto</Text>
|
||||
<Text>2022-01-01</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Deskripsi Foto</Text>
|
||||
<Text>Deskripsi Foto 1</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button color="red">
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button onClick={() => router.push('/admin/desa/gallery/foto/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 DetailFoto;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
'use client'
|
||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
||||
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';
|
||||
|
||||
|
||||
|
||||
function EditFoto() {
|
||||
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 Foto</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||
placeholder='Masukkan judul foto'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
|
||||
placeholder='Masukkan tanggal foto'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||
<KeamananEditor
|
||||
showSubmit={false}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditFoto;
|
||||
@@ -1,12 +1,29 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import JudulListTab from '../../../_com/jusulListTab';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGallery from '../../../_state/desa/gallery';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
function Foto() {
|
||||
const fotoState = useProxy(stateGallery.foto)
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
fotoState.findMany.load()
|
||||
}, [])
|
||||
|
||||
if (!fotoState.findMany.data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
@@ -26,16 +43,20 @@ function Foto() {
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
<TableTr>
|
||||
<TableTd>Foto 1</TableTd>
|
||||
<TableTd>2022-01-01</TableTd>
|
||||
<TableTd>Deskripsi Foto 1</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push('/admin/desa/gallery/foto/detail')}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
{fotoState.findMany.data?.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
10
src/app/admin/(dashboard)/desa/gallery/layout.tsx
Normal file
10
src/app/admin/(dashboard)/desa/gallery/layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
'use client'
|
||||
import LayoutTabsGallery from "../../ppid/_com/layoutTabsGallery"
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabsGallery>
|
||||
{children}
|
||||
</LayoutTabsGallery>
|
||||
)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
||||
import Foto from './foto/page';
|
||||
import Video from './video/page';
|
||||
|
||||
function Gallery() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Gallery</Title>
|
||||
<Tabs color={colors['blue-button']} variant="pills" defaultValue="foto">
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
<TabsTab value="foto" leftSection={<IconPhoto size={12} />}>
|
||||
Foto
|
||||
</TabsTab>
|
||||
<TabsTab value="video" leftSection={<IconVideo size={12} />}>
|
||||
Video
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
|
||||
<TabsPanel value="foto">
|
||||
<Foto/>
|
||||
</TabsPanel>
|
||||
|
||||
<TabsPanel value="video">
|
||||
<Video/>
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gallery;
|
||||
156
src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx
Normal file
156
src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Box, Button, Flex, Group, Image, Modal, 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 EditVideo() {
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const params = useParams()
|
||||
const [formData, setFormData] = useState({
|
||||
name: videoState.findUnique.data?.name || '',
|
||||
deskripsi: videoState.findUnique.data?.deskripsi || '',
|
||||
linkVideo: videoState.findUnique.data?.linkVideo || '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const loadVideo = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
try {
|
||||
const data = await videoState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
linkVideo: data.linkVideo || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading video:', error);
|
||||
toast.error('Gagal memuat data video');
|
||||
}
|
||||
};
|
||||
loadVideo();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
videoState.update.form = {
|
||||
...videoState.update.form,
|
||||
name: formData.name,
|
||||
deskripsi: formData.deskripsi,
|
||||
linkVideo: formData.linkVideo,
|
||||
};
|
||||
await videoState.update.update();
|
||||
toast.success('Video berhasil diperbarui!');
|
||||
router.push('/admin/desa/gallery/video');
|
||||
} catch (error) {
|
||||
console.error('Error updating video:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui video');
|
||||
}
|
||||
}
|
||||
|
||||
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 Video</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||
placeholder='Masukkan judul video'
|
||||
value={formData.name}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
name: val.target.value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Link Video Youtube <Text span c="red" inherit>*</Text></Text>}
|
||||
placeholder='Masukkan link video youtube'
|
||||
value={formData.linkVideo}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
linkVideo: val.target.value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Flex pt={5} align={"center"} gap={"xs"}>
|
||||
<Text c={"dimmed"} fw={"bold"} fz={"xs"}>Cara mendapatkan link video youtube</Text>
|
||||
<ActionIcon size={"xs"} radius={"xl"} color='black' variant='filled' onClick={() => setModalHapus(true)}>
|
||||
<Text fw={"bold"} fz={"xs"} c="white">?</Text>
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
deskripsi: val,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<Modal
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
title={<Text fw={"bold"} fz={"xl"}>Cara mendapatkan link video youtube</Text>}
|
||||
>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 1</Text>
|
||||
<Text fz={"sm"}>Buka video youtube yang ingin Anda bagikan lalu klik icon titik tiga</Text>
|
||||
<Image src="/video.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 2</Text>
|
||||
<Text fz={"sm"}>Klik bagikan</Text>
|
||||
<Image src="/Share.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 3</Text>
|
||||
<Text fz={"sm"}>Klik dibagian sematkan</Text>
|
||||
<Image src="/bagikanPostingan.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 4</Text>
|
||||
<Text fz={"sm"}>Lalu copy pada bagaian srcnya aja</Text>
|
||||
<Image src="/sematkan.png" w={300} alt="" />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditVideo;
|
||||
111
src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx
Normal file
111
src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
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 DetailVideo() {
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
videoState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
videoState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/desa/gallery/video")
|
||||
}
|
||||
}
|
||||
|
||||
if (!videoState.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 Video</Text>
|
||||
{videoState.findUnique.data ? (
|
||||
<Paper key={videoState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||
<Text fz={"lg"}>{videoState.findUnique.data?.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Link Video</Text>
|
||||
<Text fz={"lg"}>{videoState.findUnique.data?.linkVideo}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tanggal Video</Text>
|
||||
<Text fz={"lg"}>{new Date(videoState.findUnique.data?.createdAt).toDateString()}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: videoState.findUnique.data?.deskripsi }} />
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (videoState.findUnique.data) {
|
||||
setSelectedId(videoState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={videoState.delete.loading || !videoState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (videoState.findUnique.data) {
|
||||
router.push(`/admin/desa/gallery/video/${videoState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!videoState.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 berita ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailVideo;
|
||||
@@ -1,45 +1,114 @@
|
||||
'use client'
|
||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { ActionIcon, Box, Button, Flex, Group, Image, Modal, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateVideo() {
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
videoState.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
linkVideo: "",
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await videoState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/gallery/video")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Video</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||
placeholder='Masukkan judul video'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
|
||||
placeholder='Masukkan tanggal video'
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||
placeholder='Masukkan judul video'
|
||||
value={videoState.create.form.name}
|
||||
onChange={(val) => {
|
||||
videoState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Link Video Youtube <Text span c="red" inherit>*</Text></Text>}
|
||||
placeholder='Masukkan link video youtube'
|
||||
value={videoState.create.form.linkVideo}
|
||||
onChange={(val) => {
|
||||
videoState.create.form.linkVideo = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Flex pt={5} align={"center"} gap={"xs"}>
|
||||
<Text c={"dimmed"} fw={"bold"} fz={"xs"}>Cara mendapatkan link video youtube</Text>
|
||||
<ActionIcon size={"xs"} radius={"xl"} color='black' variant='filled' onClick={() => setModalHapus(true)}>
|
||||
<Text fw={"bold"} fz={"xs"} c="white">?</Text>
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||
<KeamananEditor
|
||||
showSubmit={false}
|
||||
<CreateEditor
|
||||
value={videoState.create.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
videoState.create.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<Modal
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
title={<Text fw={"bold"} fz={"xl"}>Cara mendapatkan link video youtube</Text>}
|
||||
>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 1</Text>
|
||||
<Text fz={"sm"}>Buka video youtube yang ingin Anda bagikan lalu klik icon titik tiga</Text>
|
||||
<Image src="/video.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 2</Text>
|
||||
<Text fz={"sm"}>Klik bagikan</Text>
|
||||
<Image src="/Share.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 3</Text>
|
||||
<Text fz={"sm"}>Klik dibagian sematkan</Text>
|
||||
<Image src="/bagikanPostingan.png" w={300} alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Langkah 4</Text>
|
||||
<Text fz={"sm"}>Lalu copy pada bagaian srcnya aja</Text>
|
||||
<Image src="/sematkan.png" w={300} alt="" />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailVideo() {
|
||||
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 Video</Text>
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Judul Video</Text>
|
||||
<Text>Video 1</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Tanggal Video</Text>
|
||||
<Text>2022-01-01</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Deskripsi Video</Text>
|
||||
<Text>Deskripsi Video 1</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button color="red">
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button onClick={() => router.push('/admin/desa/gallery/video/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 DetailVideo;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
'use client'
|
||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
||||
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';
|
||||
|
||||
|
||||
|
||||
function EditVideo() {
|
||||
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 Video</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||
placeholder='Masukkan judul video'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
|
||||
placeholder='Masukkan tanggal video'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||
<KeamananEditor
|
||||
showSubmit={false}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditVideo;
|
||||
@@ -1,12 +1,29 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import JudulListTab from '../../../_com/jusulListTab';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGallery from '../../../_state/desa/gallery';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
function Video() {
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
videoState.findMany.load()
|
||||
}, [])
|
||||
|
||||
if (!videoState.findMany.data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
@@ -26,16 +43,20 @@ function Video() {
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
<TableTr>
|
||||
<TableTd>Video 1</TableTd>
|
||||
<TableTd>2022-01-01</TableTd>
|
||||
<TableTd>Deskripsi Video 1</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push('/admin/desa/gallery/video/detail')}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
{videoState.findMany.data?.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
63
src/app/admin/(dashboard)/ppid/_com/layoutTabsGallery.tsx
Normal file
63
src/app/admin/(dashboard)/ppid/_com/layoutTabsGallery.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Foto",
|
||||
value: "foto",
|
||||
href: "/admin/desa/gallery/foto"
|
||||
},
|
||||
{
|
||||
label: "Video",
|
||||
value: "video",
|
||||
href: "/admin/desa/gallery/video"
|
||||
},
|
||||
|
||||
];
|
||||
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}>Gallery</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 LayoutTabsGallery;
|
||||
@@ -55,7 +55,7 @@ function GrafikBerdasarkanJenisKelaminRespondenCreate() {
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Baik"
|
||||
label="Perempuan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanJenisKelamin.create.form.perempuan}
|
||||
|
||||
@@ -7,14 +7,14 @@ import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import JudulListTab from '../../../_com/jusulListTab';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
|
||||
function GrafikBerdasarkanResponden() {
|
||||
|
||||
const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden)
|
||||
const stategrafikBerdasarkanResponden = useSnapshot(grafikBerdasarkanResponden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
@@ -39,7 +39,7 @@ function GrafikBerdasarkanResponden() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (stategrafikBerdasarkanResponden.findMany.data && stategrafikBerdasarkanResponden.findMany.data.length > 0) {
|
||||
if (stategrafikBerdasarkanResponden.findMany.data) {
|
||||
const totalSangatBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
||||
const totalBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
||||
const totalKurangBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, 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 { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -36,6 +36,8 @@ function GrafikHasilKepuasanMasyarakat() {
|
||||
stateGrafikHasilKepuasan.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateGrafikHasilKepuasan.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,14 +47,16 @@ function GrafikHasilKepuasanMasyarakat() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (stateGrafikHasilKepuasan.findMany.data && stateGrafikHasilKepuasan.findMany.data.length > 0) {
|
||||
setChartData([...stateGrafikHasilKepuasan.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
kepuasan: Number(item.kepuasan),
|
||||
}))]);
|
||||
if (stateGrafikHasilKepuasan.findMany.data) {
|
||||
setChartData(
|
||||
stateGrafikHasilKepuasan.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
kepuasan: Number(item.kepuasan),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [stateGrafikHasilKepuasan.findMany.data])
|
||||
}, [stateGrafikHasilKepuasan.findMany.data]);
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +118,7 @@ function GrafikHasilKepuasanMasyarakat() {
|
||||
<Paper style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart width={isMobile ? 300 : isTablet ? 300 : 300} height={380} data={chartData} >
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
@@ -122,6 +126,8 @@ function GrafikHasilKepuasanMasyarakat() {
|
||||
<Legend />
|
||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||
</BarChart>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -128,7 +128,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Desa_5",
|
||||
name: "Gallery",
|
||||
path: "/admin/desa/gallery"
|
||||
path: "/admin/desa/gallery/foto"
|
||||
},
|
||||
{
|
||||
id: "Desa_6",
|
||||
|
||||
32
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/create.ts
Normal file
32
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/create.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.GalleryFotoGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
imagesId: true;
|
||||
deskripsi: true;
|
||||
|
||||
};
|
||||
}>;
|
||||
async function galleryFotoCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.galleryFoto.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
deskripsi: body.deskripsi,
|
||||
imagesId: body.imagesId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create gallery foto",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default galleryFotoCreate
|
||||
53
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/del.ts
Normal file
53
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/del.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const galleryFotoDelete = async (context: Context) => {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
status: 400,
|
||||
body: "ID tidak diberikan",
|
||||
};
|
||||
}
|
||||
|
||||
const foto = await prisma.galleryFoto.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!foto) {
|
||||
return {
|
||||
status: 404,
|
||||
body: "Foto tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
// Hapus file gambar dari filesystem jika ada
|
||||
if (foto.imageGalleryFoto) {
|
||||
try {
|
||||
const filePath = path.join(foto.imageGalleryFoto.path, foto.imageGalleryFoto.name);
|
||||
await fs.unlink(filePath);
|
||||
await prisma.fileStorage.delete({
|
||||
where: { id: foto.imageGalleryFoto.id },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Gagal hapus gambar lama:", err);
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.galleryFoto.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: "Foto berhasil dihapus",
|
||||
};
|
||||
}
|
||||
|
||||
export default galleryFotoDelete
|
||||
25
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/find-many.ts
Normal file
25
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/find-many.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function galleryFotoFindMany() {
|
||||
try {
|
||||
const data = await prisma.galleryFoto.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch gallery foto",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch gallery foto",
|
||||
};
|
||||
}
|
||||
}
|
||||
export default galleryFotoFindMany
|
||||
@@ -0,0 +1,49 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function galleryFotoFindUnique(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 boleh kosong",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const data = await prisma.galleryFoto.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gallery foto tidak ditemukan",
|
||||
}, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Success fetch gallery foto by ID",
|
||||
data,
|
||||
}, { status: 200 });
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal mengambil gallery foto: " + (e instanceof Error ? e.message : 'Unknown error'),
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/index.ts
Normal file
34
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import galleryFotoCreate from "./create";
|
||||
import galleryFotoDelete from "./del";
|
||||
import galleryFotoFindMany from "./find-many";
|
||||
import galleryFotoUpdate from "./updt";
|
||||
import galleryFotoFindUnique from "./findUnique";
|
||||
|
||||
const GalleryFoto = new Elysia({ prefix: "/gallery/foto", tags: ["Desa/Gallery/Foto"] })
|
||||
.get("/find-many", galleryFotoFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await galleryFotoFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", galleryFotoCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imagesId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", galleryFotoDelete)
|
||||
.put("/:id", async (context) => {
|
||||
const response = await galleryFotoUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imagesId: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
export default GalleryFoto
|
||||
90
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/updt.ts
Normal file
90
src/app/api/[[...slugs]]/_lib/desa/gallery/foto/updt.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
type FormUpdate = Prisma.GalleryFotoGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
deskripsi: true;
|
||||
imagesId: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
async function galleryFotoUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id;
|
||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||
|
||||
const { name, deskripsi, imagesId } = body;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "ID tidak diberikan" }),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await prisma.galleryFoto.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Gallery foto tidak ditemukan",
|
||||
}),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
if (existing.imagesId && existing.imagesId !== imagesId) {
|
||||
const oldImage = existing.imageGalleryFoto;
|
||||
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.galleryFoto.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
deskripsi,
|
||||
imagesId,
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Success update gallery foto",
|
||||
data: updated,
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error updating gallery foto:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengupdate gallery foto",
|
||||
}),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
export default galleryFotoUpdate;
|
||||
30
src/app/api/[[...slugs]]/_lib/desa/gallery/video/create.ts
Normal file
30
src/app/api/[[...slugs]]/_lib/desa/gallery/video/create.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.GalleryVideoGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
deskripsi: true;
|
||||
linkVideo: true;
|
||||
}
|
||||
}>
|
||||
async function galleryVideoCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.galleryVideo.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
deskripsi: body.deskripsi,
|
||||
linkVideo: body.linkVideo,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create gallery video",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
export default galleryVideoCreate;
|
||||
34
src/app/api/[[...slugs]]/_lib/desa/gallery/video/del.ts
Normal file
34
src/app/api/[[...slugs]]/_lib/desa/gallery/video/del.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
const galleryVideoDelete = async (context: Context) => {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
status: 400,
|
||||
body: "ID tidak diberikan",
|
||||
};
|
||||
}
|
||||
|
||||
const galleryVideo = await prisma.galleryVideo.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!galleryVideo) {
|
||||
return {
|
||||
status: 404,
|
||||
body: "Gallery video tidak ditemukan",
|
||||
};
|
||||
}
|
||||
// Hapus gallery video dari database
|
||||
await prisma.galleryVideo.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: "Gallery video berhasil dihapus",
|
||||
};
|
||||
}
|
||||
export default galleryVideoDelete;
|
||||
@@ -0,0 +1,22 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function galleryVideoFindMany() {
|
||||
try {
|
||||
const data = await prisma.galleryVideo.findMany({
|
||||
where: { isActive: true },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch gallery video",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch gallery video",
|
||||
};
|
||||
}
|
||||
}
|
||||
export default galleryVideoFindMany;
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function galleryVideoFindUnique(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 boleh kosong",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const data = await prisma.galleryVideo.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gallery video tidak ditemukan",
|
||||
}, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Success fetch gallery video by ID",
|
||||
data,
|
||||
}, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal mengambil gallery video: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
39
src/app/api/[[...slugs]]/_lib/desa/gallery/video/index.ts
Normal file
39
src/app/api/[[...slugs]]/_lib/desa/gallery/video/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import galleryVideoFindMany from "./find-many";
|
||||
import galleryVideoFindUnique from "./findUnique";
|
||||
import galleryVideoCreate from "./create";
|
||||
import galleryVideoDelete from "./del";
|
||||
import galleryVideoUpdate from "./updt";
|
||||
|
||||
const GalleryVideo = new Elysia({
|
||||
prefix: "/gallery/video",
|
||||
tags: ["Desa/Gallery/Video"],
|
||||
})
|
||||
.get("/find-many", galleryVideoFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await galleryVideoFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", galleryVideoCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
linkVideo: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", galleryVideoDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await galleryVideoUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
linkVideo: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
export default GalleryVideo;
|
||||
85
src/app/api/[[...slugs]]/_lib/desa/gallery/video/updt.ts
Normal file
85
src/app/api/[[...slugs]]/_lib/desa/gallery/video/updt.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = Prisma.GalleryVideoGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
deskripsi: true;
|
||||
linkVideo: true;
|
||||
}
|
||||
}>;
|
||||
|
||||
async function galleryVideoUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||
|
||||
const {
|
||||
name,
|
||||
deskripsi,
|
||||
linkVideo,
|
||||
} = body;
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), {
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const existing = await prisma.galleryVideo.findUnique({
|
||||
where: {id}
|
||||
})
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Gallery video tidak ditemukan",
|
||||
}), {
|
||||
status: 404,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await prisma.galleryVideo.update({
|
||||
where: {id},
|
||||
data: {
|
||||
name,
|
||||
deskripsi,
|
||||
linkVideo,
|
||||
}
|
||||
})
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Gallery video berhasil diupdate",
|
||||
data: updated,
|
||||
}), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Gagal mengupdate gallery video: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
export default galleryVideoUpdate;
|
||||
@@ -3,11 +3,15 @@ import Berita from "./berita";
|
||||
import Pengumuman from "./pengumuman";
|
||||
import ProfileDesa from "./profile/profile_desa";
|
||||
import PotensiDesa from "./potensi";
|
||||
import GalleryFoto from "./gallery/foto";
|
||||
import GalleryVideo from "./gallery/video";
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
.use(Berita)
|
||||
.use(Pengumuman)
|
||||
.use(ProfileDesa)
|
||||
.use(PotensiDesa)
|
||||
.use(GalleryFoto)
|
||||
.use(GalleryVideo)
|
||||
|
||||
export default Desa;
|
||||
|
||||
Reference in New Issue
Block a user