UI Desa Gallery Done
API Desa Gallery Done
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
"@mantine/carousel": "^7.16.2",
|
"@mantine/carousel": "^7.16.2",
|
||||||
"@mantine/charts": "^7.17.1",
|
"@mantine/charts": "^7.17.1",
|
||||||
"@mantine/core": "^7.17.4",
|
"@mantine/core": "^7.17.4",
|
||||||
"@mantine/dates": "^7.17.4",
|
"@mantine/dates": "^8.1.0",
|
||||||
"@mantine/dropzone": "^7.17.0",
|
"@mantine/dropzone": "^7.17.0",
|
||||||
"@mantine/form": "^8.1.0",
|
"@mantine/form": "^8.1.0",
|
||||||
"@mantine/hooks": "^7.17.4",
|
"@mantine/hooks": "^7.17.4",
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ model FileStorage {
|
|||||||
Posyandu Posyandu[]
|
Posyandu Posyandu[]
|
||||||
ProfilePPID ProfilePPID[]
|
ProfilePPID ProfilePPID[]
|
||||||
StrukturPPID StrukturPPID[]
|
StrukturPPID StrukturPPID[]
|
||||||
|
|
||||||
|
GalleryFoto GalleryFoto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU PPID ========================================= //
|
//========================================= MENU PPID ========================================= //
|
||||||
@@ -80,6 +82,7 @@ model StrukturPPID {
|
|||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= VISI MISI PPID ========================================= //
|
// ========================================= VISI MISI PPID ========================================= //
|
||||||
model VisiMisiPPID {
|
model VisiMisiPPID {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -334,51 +337,28 @@ model CategoryPengumuman {
|
|||||||
isActive Boolean @default(true)
|
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 ========================================= //
|
// ========================================= GALLERY ========================================= //
|
||||||
model GalleryFoto {
|
model GalleryFoto {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
image String
|
deskripsi String @db.Text
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
imagesId String? @unique
|
imagesId String? @unique
|
||||||
imageGalleryFoto Images? @relation(fields: [imagesId], references: [id])
|
imageGalleryFoto FileStorage? @relation(fields: [imagesId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model GalleryVideo {
|
model GalleryVideo {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
video String
|
deskripsi String @db.Text
|
||||||
|
linkVideo String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
videosId String? @unique
|
|
||||||
videosGalleryVideo Videos? @relation(fields: [videosId], references: [id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= MENU KESEHATAN ========================================= //
|
// ========================================= 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) {
|
if (id) {
|
||||||
toast.success("Success create");
|
toast.success("Success create");
|
||||||
grafikBerdasarkanResponden.create.form = {
|
grafikBerdasarkanResponden.create.form = {
|
||||||
...defaultForm
|
sangatbaik: "",
|
||||||
|
baik: "",
|
||||||
|
kurangbaik: "",
|
||||||
|
tidakbaik: "",
|
||||||
};
|
};
|
||||||
grafikBerdasarkanResponden.findMany.load();
|
grafikBerdasarkanResponden.findMany.load();
|
||||||
return id;
|
return id;
|
||||||
@@ -109,7 +112,7 @@ const grafikBerdasarkanResponden = proxy({
|
|||||||
form: {...defaultForm},
|
form: {...defaultForm},
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId() {
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
const id = this.id;
|
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,41 +1,104 @@
|
|||||||
'use client'
|
'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 colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
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 { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateFoto() {
|
function CreateFoto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
const router = useRouter();
|
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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</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"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Foto</Title>
|
<Title order={4}>Create Foto</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||||
placeholder='Masukkan judul foto'
|
placeholder='Masukkan judul foto'
|
||||||
|
value={fotoState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
fotoState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<FileInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
placeholder='Masukkan tanggal foto'
|
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>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||||
<KeamananEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={fotoState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
fotoState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -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'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import JudulListTab from '../../../_com/jusulListTab';
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateGallery from '../../../_state/desa/gallery';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function Foto() {
|
function Foto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
fotoState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!fotoState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -26,16 +43,20 @@ function Foto() {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{fotoState.findMany.data?.map((item) => (
|
||||||
<TableTd>Foto 1</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>2022-01-01</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>Deskripsi Foto 1</TableTd>
|
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/foto/detail')}>
|
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</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,44 +1,113 @@
|
|||||||
'use client'
|
'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 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 { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateVideo() {
|
function CreateVideo() {
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</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"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Video</Title>
|
<Title order={4}>Create Video</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||||
placeholder='Masukkan judul video'
|
placeholder='Masukkan judul video'
|
||||||
/>
|
value={videoState.create.form.name}
|
||||||
<TextInput
|
onChange={(val) => {
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
|
videoState.create.form.name = val.target.value;
|
||||||
placeholder='Masukkan tanggal video'
|
}}
|
||||||
/>
|
/>
|
||||||
|
<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>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||||
<KeamananEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={videoState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
videoState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</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>
|
</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'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import JudulListTab from '../../../_com/jusulListTab';
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateGallery from '../../../_state/desa/gallery';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function Video() {
|
function Video() {
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
videoState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!videoState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -26,16 +43,20 @@ function Video() {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{videoState.findMany.data?.map((item) => (
|
||||||
<TableTd>Video 1</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>2022-01-01</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>Deskripsi Video 1</TableTd>
|
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/video/detail')}>
|
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</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
|
<TextInput
|
||||||
label="Baik"
|
label="Perempuan"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="masukkan jumlah"
|
placeholder="masukkan jumlah"
|
||||||
value={stategrafikBerdasarkanJenisKelamin.create.form.perempuan}
|
value={stategrafikBerdasarkanJenisKelamin.create.form.perempuan}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useSnapshot } from 'valtio';
|
||||||
import JudulListTab from '../../../_com/jusulListTab';
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||||
|
|
||||||
function GrafikBerdasarkanResponden() {
|
function GrafikBerdasarkanResponden() {
|
||||||
|
|
||||||
const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden)
|
const stategrafikBerdasarkanResponden = useSnapshot(grafikBerdasarkanResponden)
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
@@ -39,7 +39,7 @@ function GrafikBerdasarkanResponden() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
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 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 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);
|
const totalKurangBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
||||||
import colors from '@/con/colors';
|
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 { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -36,6 +36,8 @@ function GrafikHasilKepuasanMasyarakat() {
|
|||||||
stateGrafikHasilKepuasan.delete.byId(selectedId)
|
stateGrafikHasilKepuasan.delete.byId(selectedId)
|
||||||
setModalHapus(false)
|
setModalHapus(false)
|
||||||
setSelectedId(null)
|
setSelectedId(null)
|
||||||
|
|
||||||
|
stateGrafikHasilKepuasan.findMany.load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +47,16 @@ function GrafikHasilKepuasanMasyarakat() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stateGrafikHasilKepuasan.findMany.data && stateGrafikHasilKepuasan.findMany.data.length > 0) {
|
if (stateGrafikHasilKepuasan.findMany.data) {
|
||||||
setChartData([...stateGrafikHasilKepuasan.findMany.data.map((item) => ({
|
setChartData(
|
||||||
|
stateGrafikHasilKepuasan.findMany.data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
kepuasan: Number(item.kepuasan),
|
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'}>
|
<Paper style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
|
<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} >
|
<BarChart width={isMobile ? 300 : isTablet ? 300 : 300} height={380} data={chartData} >
|
||||||
<XAxis dataKey="label" />
|
<XAxis dataKey="label" />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
@@ -122,6 +126,8 @@ function GrafikHasilKepuasanMasyarakat() {
|
|||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
|
) : (
|
||||||
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "Desa_5",
|
id: "Desa_5",
|
||||||
name: "Gallery",
|
name: "Gallery",
|
||||||
path: "/admin/desa/gallery"
|
path: "/admin/desa/gallery/foto"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Desa_6",
|
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 Pengumuman from "./pengumuman";
|
||||||
import ProfileDesa from "./profile/profile_desa";
|
import ProfileDesa from "./profile/profile_desa";
|
||||||
import PotensiDesa from "./potensi";
|
import PotensiDesa from "./potensi";
|
||||||
|
import GalleryFoto from "./gallery/foto";
|
||||||
|
import GalleryVideo from "./gallery/video";
|
||||||
|
|
||||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||||
.use(Berita)
|
.use(Berita)
|
||||||
.use(Pengumuman)
|
.use(Pengumuman)
|
||||||
.use(ProfileDesa)
|
.use(ProfileDesa)
|
||||||
.use(PotensiDesa)
|
.use(PotensiDesa)
|
||||||
|
.use(GalleryFoto)
|
||||||
|
.use(GalleryVideo)
|
||||||
|
|
||||||
export default Desa;
|
export default Desa;
|
||||||
|
|||||||
Reference in New Issue
Block a user