This commit is contained in:
Anang Yusman 2025-11-20 14:57:16 +08:00
parent 8cd20ddd8f
commit b623304e9d
17 changed files with 471 additions and 299 deletions

View File

@ -14,7 +14,6 @@ export default function AgentPage() {
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
console.log("Banner Data:", data); console.log("Banner Data:", data);
// TODO: kirim data ke API di sini
}; };
return ( return (

View File

@ -25,7 +25,6 @@ export default function GaleryPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{/* Tombol buka dialog */}
<Button <Button
className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2" className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"
onClick={() => setOpenDialog(true)} onClick={() => setOpenDialog(true)}
@ -34,13 +33,11 @@ export default function GaleryPage() {
Tambah Galeri Baru Tambah Galeri Baru
</Button> </Button>
{/* Komponen Galeri */}
<Galery /> <Galery />
</div> </div>
</div> </div>
</div> </div>
{/* Dialog Tambah Galeri */}
<GaleriDialog <GaleriDialog
open={openDialog} open={openDialog}
onClose={() => setOpenDialog(false)} onClose={() => setOpenDialog(false)}

View File

@ -32,23 +32,13 @@ export default function AgentDetailDialog({
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="p-0 max-w-lg overflow-hidden rounded-2xl"> <DialogContent className="p-0 max-w-lg overflow-hidden rounded-2xl">
{/* HEADER */}
<div className="bg-gradient-to-r from-[#0f6c75] to-[#145f66] text-white px-6 py-5 relative"> <div className="bg-gradient-to-r from-[#0f6c75] to-[#145f66] text-white px-6 py-5 relative">
{/* CLOSE BUTTON */}
{/* <button
onClick={() => onOpenChange(false)}
className="absolute top-4 right-5 text-white/80 hover:text-white text-xl"
>
</button> */}
<DialogHeader> <DialogHeader>
<DialogTitle className="text-white text-xl font-semibold"> <DialogTitle className="text-white text-xl font-semibold">
Detail Agen Detail Agen
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{/* STATUS BADGE */}
<div className="mt-2 bg-white/20 text-white px-3 py-1 rounded-full text-xs inline-flex items-center gap-2"> <div className="mt-2 bg-white/20 text-white px-3 py-1 rounded-full text-xs inline-flex items-center gap-2">
<span <span
className={`w-2 h-2 rounded-full ${ className={`w-2 h-2 rounded-full ${
@ -59,9 +49,7 @@ export default function AgentDetailDialog({
</div> </div>
</div> </div>
{/* BODY */}
<div className="p-6 text-center"> <div className="p-6 text-center">
{/* FOTO PROFIL */}
<div className="flex justify-center"> <div className="flex justify-center">
<Image <Image
src={data.imageUrl} src={data.imageUrl}
@ -72,16 +60,12 @@ export default function AgentDetailDialog({
/> />
</div> </div>
{/* NAMA */}
<h2 className="text-xl font-semibold mt-5">{data.name}</h2> <h2 className="text-xl font-semibold mt-5">{data.name}</h2>
{/* JABATAN */}
<p className="text-gray-600">{data.position}</p> <p className="text-gray-600">{data.position}</p>
{/* NOMOR TELEPON */}
<p className="text-gray-700 mt-2 font-medium">{data.phone}</p> <p className="text-gray-700 mt-2 font-medium">{data.phone}</p>
{/* JENIS AGEN */}
<div className="text-left mt-5"> <div className="text-left mt-5">
<p className="font-semibold mb-2">Jenis Agen</p> <p className="font-semibold mb-2">Jenis Agen</p>

View File

@ -1,3 +1,5 @@
"use client";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -7,8 +9,37 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import Image from "next/image"; import Image from "next/image";
import { CheckCircle } from "lucide-react"; import { CheckCircle } from "lucide-react";
import { useEffect, useState } from "react";
import { getGaleryFileData } from "@/service/galery";
export function DialogDetailGaleri({ open, onClose, data }: any) { export function DialogDetailGaleri({ open, onClose, data }: any) {
const [images, setImages] = useState<any[]>([]);
const fetchImages = async () => {
try {
const res = await getGaleryFileData(data.id);
const allImages = res?.data?.data ?? [];
const filteredImages = allImages.filter(
(img: any) => img.gallery_id === data.id
);
setImages(filteredImages);
} catch (e) {
console.error("Error fetch files:", e);
}
};
useEffect(() => {
if (open && data?.id) {
fetchImages();
}
}, [open, data]);
const openFile = (url: string) => {
window.open(url, "_blank");
};
return ( return (
<Dialog open={open} onOpenChange={onClose}> <Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-3xl rounded-2xl p-0 overflow-hidden"> <DialogContent className="max-w-3xl rounded-2xl p-0 overflow-hidden">
@ -18,15 +49,35 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
</div> </div>
<div className="px-6 py-6 space-y-6"> <div className="px-6 py-6 space-y-6">
{/* Images */} {/* Images List */}
<div>
<p className="font-medium mb-2">Daftar Gambar</p>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div className="relative h-28 w-full"> {images.length === 0 && (
<p className="text-gray-500 col-span-3 text-center">
Tidak ada gambar.
</p>
)}
{images.map((img) => (
<div
key={img.id}
className="relative h-32 w-full cursor-pointer group"
onClick={() => openFile(img.image_url)}
>
<Image <Image
src={data.image_url} src={img.image_url}
alt="Galery Image" alt={img.title}
fill fill
className="object-cover rounded-lg" className="object-cover rounded-lg"
/> />
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 text-white flex items-center justify-center text-sm transition">
Lihat File
</div>
</div>
))}
</div> </div>
</div> </div>
@ -38,7 +89,7 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
{/* Deskripsi */} {/* Deskripsi */}
<div> <div>
<p className="font-medium text-sm text-gray-700">Deskripsi</p> <p className="font-medium text-sm text-gray-700">Deskripsi</p>
<p className="text-gray-600">{data.desc}</p> <p className="text-gray-600">{data.description}</p>
</div> </div>
{/* Tanggal Upload */} {/* Tanggal Upload */}
@ -81,7 +132,6 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
</div> </div>
</div> </div>
{/* Footer */}
<DialogFooter className="px-6 pb-6"> <DialogFooter className="px-6 pb-6">
<button <button
onClick={onClose} onClick={onClose}

View File

@ -9,54 +9,89 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Upload } from "lucide-react"; import { Upload, X } from "lucide-react";
import { useState, useRef } from "react"; import { useState, useRef, useEffect } from "react";
import { createGalery } from "@/service/galery"; import { createGalery, uploadGaleryFile } from "@/service/galery";
export function GaleriDialog({ open, onClose, onSubmit }: any) { export function GaleriDialog({ open, onClose, onSubmit }: any) {
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [desc, setDesc] = useState(""); const [description, setDescription] = useState("");
const [file, setFile] = useState<File | null>(null); const [files, setFiles] = useState<File[]>([]);
const [previews, setPreviews] = useState<string[]>([]);
const fileRef = useRef<HTMLInputElement>(null); const fileRef = useRef<HTMLInputElement>(null);
const handleSubmit = async () => { useEffect(() => {
if (!file) { if (!files || files.length === 0) {
alert("File wajib diupload!"); setPreviews([]);
return; return;
} }
const form = new FormData(); const objectUrls = files.map((file) => URL.createObjectURL(file));
form.append("gallery_id", "1"); // nilai default (bisa dinamis) setPreviews(objectUrls);
form.append("title", title);
form.append("desc", desc);
form.append("file", file);
try { return () => {
const res = await createGalery(form); objectUrls.forEach((url) => URL.revokeObjectURL(url));
};
}, [files]);
console.log("Upload Success:", res?.data); const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
onSubmit(); // tutup dialog if (!files) return;
} catch (error: any) { setFiles((prev: File[]) => [...prev, ...Array.from(files)]);
console.error("Upload failed:", error);
alert("Gagal mengupload file");
}
}; };
const handleFileChange = (e: any) => { const removeFile = (index: number) => {
const selected = e.target.files[0]; setFiles((prev) => prev.filter((_, i) => i !== index));
if (selected) setFile(selected); };
const handleSubmit = async () => {
try {
if (!title) return alert("Judul wajib diisi!");
const formData = new FormData();
formData.append("title", title);
formData.append("description", description);
const res = await createGalery(formData);
const galleryId = res?.data?.data?.id;
if (!galleryId) {
alert("Galeri gagal dibuat");
return;
}
console.log("Galeri Created:", galleryId);
for (const file of files) {
const fileForm = new FormData();
fileForm.append("gallery_id", galleryId);
fileForm.append("title", title);
fileForm.append("file", file);
const uploadRes = await uploadGaleryFile(galleryId, fileForm);
console.log("File Uploaded:", uploadRes?.data);
}
onSubmit();
setTitle("");
setDescription("");
setFiles([]);
} catch (error) {
console.error("Submit failed:", error);
alert("Gagal mengupload galeri");
}
}; };
return ( return (
<Dialog open={open} onOpenChange={onClose}> <Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-2xl rounded-2xl p-0 overflow-hidden"> <DialogContent className="max-w-2xl rounded-2xl p-0 overflow-hidden">
{/* Header */} {/* HEADER */}
<div className="bg-[#1F6779] text-white px-6 py-4 flex justify-between items-center"> <div className="bg-[#1F6779] text-white px-6 py-4 flex justify-between items-center">
<DialogTitle className="text-white">Tambah Galeri</DialogTitle> <DialogTitle className="text-white">Tambah Galeri</DialogTitle>
</div> </div>
{/* BODY */}
<div className="px-6 py-6 space-y-6"> <div className="px-6 py-6 space-y-6">
{/* Judul */} {/* Judul */}
<div> <div>
@ -79,15 +114,15 @@ export function GaleriDialog({ open, onClose, onSubmit }: any) {
<Input <Input
placeholder="Masukkan deskripsi galeri" placeholder="Masukkan deskripsi galeri"
className="mt-1 h-12" className="mt-1 h-12"
value={desc} value={description}
onChange={(e) => setDesc(e.target.value)} onChange={(e) => setDescription(e.target.value)}
/> />
</div> </div>
{/* Upload */} {/* Upload File */}
<div> <div>
<label className="font-medium text-sm"> <label className="font-medium text-sm">
Upload File <span className="text-red-500">*</span> Upload File (opsional)
</label> </label>
<div <div
@ -107,18 +142,39 @@ export function GaleriDialog({ open, onClose, onSubmit }: any) {
className="hidden" className="hidden"
onChange={handleFileChange} onChange={handleFileChange}
accept="image/*" accept="image/*"
multiple
/> />
</div> </div>
{file && ( {/* Preview Gambar */}
<p className="text-xs mt-2 text-green-700"> {previews.length > 0 && (
File dipilih: {file.name} <div className="mt-4 flex flex-wrap gap-4">
</p> {previews.map((src, i) => (
<div
key={i}
className="relative w-24 h-24 rounded-lg overflow-hidden border border-gray-300"
>
<img
src={src}
alt={`preview-${i}`}
className="object-cover w-full h-full"
/>
<button
onClick={() => removeFile(i)}
className="absolute top-1 right-1 bg-red-600 rounded-full p-1 text-white hover:bg-red-700 transition"
title="Hapus gambar"
type="button"
>
<X size={16} />
</button>
</div>
))}
</div>
)} )}
</div> </div>
</div> </div>
{/* Footer */} {/* FOOTER */}
<DialogFooter className="flex justify-between px-6 pb-6 gap-4"> <DialogFooter className="flex justify-between px-6 pb-6 gap-4">
<Button <Button
variant="ghost" variant="ghost"

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { import {
Dialog, Dialog,
@ -10,90 +10,119 @@ import {
DialogFooter, DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { updateGalery } from "@/service/galery"; import {
getGaleryFileData,
updateGalery,
updateUploadGaleryFile,
} from "@/service/galery";
export function DialogUpdateGaleri({ open, onClose, data, onUpdated }: any) { export function DialogUpdateGaleri({ open, onClose, data, onUpdated }: any) {
const [title, setTitle] = useState(data.title); const [title, setTitle] = useState(data.title || "");
const [desc, setDesc] = useState(data.desc); const [description, setDescription] = useState(data.desc || "");
const [files, setFiles] = useState(data.files || []); const [oldFiles, setOldFiles] = useState<any[]>([]);
const [newFiles, setNewFiles] = useState<File[]>([]); const [newFiles, setNewFiles] = useState<File[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const fetchOldFiles = async () => {
try {
const res = await getGaleryFileData(data.id);
const allImages = res?.data?.data ?? [];
const filteredImages = allImages.filter(
(img: any) => img.gallery_id === data.id
);
setOldFiles(filteredImages);
} catch (e) {
console.error("Error fetch files:", e);
}
};
useEffect(() => {
if (open && data?.id) {
fetchOldFiles();
setTitle(data.title || "");
setDescription(data.description || "");
}
}, [open, data]);
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => { const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const uploaded = Array.from(e.target.files || []) as File[]; const uploaded = Array.from(e.target.files || []) as File[];
setNewFiles((prev) => [...prev, ...uploaded]); setNewFiles((prev) => [...prev, ...uploaded]);
}; };
const removeOldFile = (id: number) => { const removeOldFile = (id: number) => {
setFiles(files.filter((f: any) => f.id !== id)); setOldFiles((prev) => prev.filter((f) => f.id !== id));
}; };
const removeNewFile = (index: number) => { const removeNewFile = (index: number) => {
setNewFiles(newFiles.filter((_, i) => i !== index)); setNewFiles((prev) => prev.filter((_, i) => i !== index));
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
setLoading(true); setLoading(true);
const form = new FormData(); const formMain = new FormData();
form.append("title", title); formMain.append("title", title);
form.append("desc", desc); formMain.append("description", description);
// file lama yang masih dipakai formMain.append("old_files", JSON.stringify(oldFiles.map((f) => f.id)));
form.append("old_files", JSON.stringify(files.map((f: any) => f.id)));
// file baru await updateGalery(data.id, formMain);
newFiles.forEach((file) => {
form.append("files", file);
});
const res = await updateGalery(data.id, form); for (const file of newFiles) {
const formFile = new FormData();
formFile.append("gallery_id", String(data.id));
formFile.append("title", title);
formFile.append("file", file);
await updateUploadGaleryFile(data.id, formFile);
}
setLoading(false); setLoading(false);
onClose(); onClose();
if (onUpdated) onUpdated(); // refresh list if (onUpdated) onUpdated();
} catch (error) { } catch (err) {
setLoading(false); setLoading(false);
console.error("Error update:", error); console.error("Error update gallery:", err);
alert("Gagal menyimpan perubahan galeri");
} }
}; };
return ( return (
<Dialog open={open} onOpenChange={onClose}> <Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-xl rounded-2xl"> <DialogContent className="max-w-xl rounded-2xl">
<DialogHeader className="mb-4"> <DialogHeader>
<DialogTitle>Edit Banner</DialogTitle> <DialogTitle>Edit Galeri</DialogTitle>
</DialogHeader> </DialogHeader>
{/* Form */} {/* FORM */}
<div className="space-y-4"> <div className="space-y-4">
{/* Title */} {/* TITLE */}
<div> <div>
<label className="font-medium text-sm">Judul Galeri *</label> <label className="font-medium text-sm">Judul Galeri *</label>
<input <input
className="w-full border rounded-lg px-3 py-2 mt-1" className="w-full border rounded-lg px-3 py-2 mt-1"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
required
/> />
</div> </div>
{/* Desc */} {/* DESCRIPTION */}
<div> <div>
<label className="font-medium text-sm">Deskripsi Galeri *</label> <label className="font-medium text-sm">Deskripsi Galeri *</label>
<textarea <textarea
className="w-full border rounded-lg px-3 py-2 mt-1" className="w-full border rounded-lg px-3 py-2 mt-1"
rows={3} rows={3}
value={desc} value={description}
onChange={(e) => setDesc(e.target.value)} onChange={(e) => setDescription(e.target.value)}
/> />
</div> </div>
{/* Upload */} {/* UPLOAD FILE */}
<div> <div>
<label className="font-medium text-sm">Upload File *</label> <label className="font-medium text-sm">Upload File</label>
<label className="mt-2 flex flex-col items-center justify-center border border-dashed rounded-xl py-6 cursor-pointer"> <label className="mt-2 flex flex-col items-center justify-center border border-dashed rounded-xl py-6 cursor-pointer">
<input <input
type="file" type="file"
@ -110,31 +139,36 @@ export function DialogUpdateGaleri({ open, onClose, data, onUpdated }: any) {
</div> </div>
</label> </label>
{/* EXISTING FILES */} {/* OLD FILES */}
<div className="flex gap-3 mt-3"> {oldFiles.length > 0 && (
<div className="relative w-20 h-20"> <div className="flex gap-3 mt-3 flex-wrap">
{oldFiles.map((f) => (
<div key={f.id} className="relative w-20 h-20">
<Image <Image
src={data.image_url} src={f.image_url}
alt="" alt={f.title}
fill fill
className="object-cover rounded-lg" className="object-cover rounded-lg"
/> />
<button <button
onClick={() => removeOldFile(data.id)} onClick={() => removeOldFile(f.id)}
className="absolute -top-2 -right-2 bg-red-600 text-white rounded-full p-1" className="absolute -top-2 -right-2 bg-red-600 text-white rounded-full p-1"
> >
<X size={12} /> <X size={12} />
</button> </button>
</div> </div>
))}
</div> </div>
)}
{/* NEW FILES */} {/* NEW FILES */}
<div className="flex gap-3 mt-3"> {newFiles.length > 0 && (
<div className="flex gap-3 mt-3 flex-wrap">
{newFiles.map((file, idx) => ( {newFiles.map((file, idx) => (
<div key={idx} className="relative w-20 h-20"> <div key={idx} className="relative w-20 h-20">
<Image <Image
src={URL.createObjectURL(file)} src={URL.createObjectURL(file)}
alt="" alt={file.name}
fill fill
className="object-cover rounded-lg" className="object-cover rounded-lg"
/> />
@ -147,11 +181,12 @@ export function DialogUpdateGaleri({ open, onClose, data, onUpdated }: any) {
</div> </div>
))} ))}
</div> </div>
)}
</div> </div>
</div> </div>
{/* Footer */} {/* FOOTER */}
<DialogFooter className="mt-6"> <DialogFooter className="mt-6 flex gap-2">
<button <button
onClick={onClose} onClick={onClose}
className="px-6 py-2 rounded-lg bg-gray-200 text-gray-700" className="px-6 py-2 rounded-lg bg-gray-200 text-gray-700"

View File

@ -17,6 +17,8 @@ import { createAgent } from "@/service/agent";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { Label } from "@/components/ui/label";
import { UploadCloud } from "lucide-react";
type AgentFormValues = { type AgentFormValues = {
fullName: string; fullName: string;
@ -29,8 +31,10 @@ type AgentFormValues = {
const agentTypes = ["After Sales", "Sales", "Spv", "Branch Manager"]; const agentTypes = ["After Sales", "Sales", "Spv", "Branch Manager"];
export default function AddAgentForm() { export default function AddAgentForm() {
const [previewImg, setPreviewImg] = useState<string | null>(null); // const [previewImg, setPreviewImg] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const [file, setFile] = useState<File | null>(null);
const [previewImg, setPreview] = useState<string | null>(null);
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const form = useForm<AgentFormValues>({ const form = useForm<AgentFormValues>({
@ -43,29 +47,37 @@ export default function AddAgentForm() {
}, },
}); });
const handleImageChange = (file?: File) => { const handleFileChange = (e: any) => {
if (!file) return; const selected = e.target.files[0];
form.setValue("profileImage", file); if (selected) setFile(selected);
};
const preview = URL.createObjectURL(file); const handleRemoveFile = () => {
setPreviewImg(preview); setFile(null);
setPreview(null);
}; };
const onSubmit = async (data: AgentFormValues) => { const onSubmit = async (data: AgentFormValues) => {
try { try {
const payload = { const formData = new FormData();
name: data.fullName, formData.append("name", data.fullName);
job_title: data.position, formData.append("job_title", data.position);
phone: data.phone, formData.append("phone", data.phone);
agent_type: data.roles, // langsung array
profile_picture_path: data.profileImage ? data.profileImage.name : "",
};
const res = await createAgent(payload); data.roles.forEach((role) => {
formData.append("agent_type[]", role);
});
if (file) {
formData.append("file", file);
}
const res = await createAgent(formData);
console.log("SUCCESS:", res); console.log("SUCCESS:", res);
} catch (err) { } catch (err) {
console.error("ERROR CREATE AGENT:", err); console.error("ERROR CREATE AGENT:", err);
} }
successSubmit("/admin/agent"); successSubmit("/admin/agent");
}; };
@ -90,9 +102,7 @@ export default function AddAgentForm() {
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* GRID INPUT */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5"> <div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{/* Nama */}
<FormField <FormField
control={form.control} control={form.control}
name="fullName" name="fullName"
@ -110,7 +120,6 @@ export default function AddAgentForm() {
)} )}
/> />
{/* Jabatan */}
<FormField <FormField
control={form.control} control={form.control}
name="position" name="position"
@ -150,7 +159,6 @@ export default function AddAgentForm() {
/> />
</div> </div>
{/* CHECKBOX ROLE */}
<div> <div>
<FormLabel> <FormLabel>
Pilih Jenis Agen <span className="text-red-500">*</span> Pilih Jenis Agen <span className="text-red-500">*</span>
@ -189,52 +197,32 @@ export default function AddAgentForm() {
</div> </div>
</div> </div>
{/* UPLOAD FOTO */} <div>
<div className="space-y-3"> <Label className="text-gray-700">
<FormLabel> Upload Foto Profil <span className="text-red-500">*</span>
Upload Foto Profile <span className="text-red-500">*</span> </Label>
</FormLabel> <label
htmlFor="uploadFile"
<div className="relative border-2 border-dashed rounded-xl bg-slate-50 dark:bg-neutral-800 p-6 flex flex-col items-center cursor-pointer"> className="mt-1 border-2 border-dashed border-gray-300 rounded-xl p-6 flex flex-col items-center justify-center text-gray-500 cursor-pointer hover:border-[#1F6779]/50 transition"
{previewImg ? (
<img
src={previewImg}
className="w-32 h-32 rounded-full object-cover mb-4"
/>
) : (
<div className="flex flex-col items-center">
<div className="bg-teal-200 p-4 rounded-full mb-3">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.8}
stroke="currentColor"
className="w-8 h-8 text-teal-700"
> >
<path <UploadCloud className="w-10 h-10 text-[#1F6779] mb-2" />
strokeLinecap="round" <p className="text-sm font-medium">
strokeLinejoin="round"
d="M12 16.5v-9m0 0L9 10m3-2.5l3 2.5m1.5 9H6a1.5 1.5 0 01-1.5-1.5v-12A1.5 1.5 0 016 3h12a1.5 1.5 0 011.5 1.5v12a1.5 1.5 0 01-1.5 1.5z"
/>
</svg>
</div>
<p className="text-gray-600 dark:text-gray-300">
Klik untuk upload atau drag & drop Klik untuk upload atau drag & drop
</p> </p>
<p className="text-xs text-gray-500 mt-1"> <p className="text-xs text-gray-400 mt-1">PNG, JPG (max 2 MB)</p>
PNG, JPG (max 2 MB)
</p>
</div>
)}
<input <input
id="uploadFile"
type="file" type="file"
className="absolute inset-0 opacity-0 cursor-pointer" accept="image/png, image/jpeg"
accept="image/*" className="hidden"
onChange={(e) => handleImageChange(e.target.files?.[0])} onChange={handleFileChange}
/> />
</div> {file && (
<p className="mt-2 text-xs text-[#1F6779] font-medium">
{file.name}
</p>
)}
</label>
</div> </div>
{/* BUTTON */} {/* BUTTON */}

View File

@ -17,6 +17,7 @@ import { getAgentById, updateAgent } from "@/service/agent";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { X } from "lucide-react";
type AgentFormValues = { type AgentFormValues = {
fullName: string; fullName: string;
@ -32,6 +33,8 @@ export default function UpdateAgentForm({ id }: { id: number }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [previewImg, setPreviewImg] = useState<string | null>(null); const [previewImg, setPreviewImg] = useState<string | null>(null);
const [agentData, setAgentData] = useState<any>(null); const [agentData, setAgentData] = useState<any>(null);
const [file, setFile] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
@ -46,9 +49,6 @@ export default function UpdateAgentForm({ id }: { id: number }) {
}, },
}); });
// ================================
// 🔥 FETCH API & SET DEFAULT VALUES
// ================================
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
try { try {
@ -61,7 +61,6 @@ export default function UpdateAgentForm({ id }: { id: number }) {
setAgentData(res.data); setAgentData(res.data);
// set form default values
form.reset({ form.reset({
fullName: res?.data?.data?.name, fullName: res?.data?.data?.name,
position: res?.data?.data?.job_title, position: res?.data?.data?.job_title,
@ -71,7 +70,7 @@ export default function UpdateAgentForm({ id }: { id: number }) {
}); });
console.log("name", res?.data?.data?.name); console.log("name", res?.data?.data?.name);
setPreviewImg(res.data.profile_picture_path || null); setPreviewImg(res.data.data.profile_picture_path || null);
} catch (err) { } catch (err) {
console.error("ERROR FETCH DATA AGENT:", err); console.error("ERROR FETCH DATA AGENT:", err);
} finally { } finally {
@ -82,27 +81,37 @@ export default function UpdateAgentForm({ id }: { id: number }) {
fetchData(); fetchData();
}, [id, form]); }, [id, form]);
const handleImageChange = (file?: File) => { const handleFileChange = (e: any) => {
if (!file) return; const selected = e.target.files[0];
form.setValue("profileImage", file); if (selected) {
setFile(selected);
setPreview(URL.createObjectURL(selected));
}
};
const preview = URL.createObjectURL(file); const handleRemoveFile = () => {
setPreviewImg(preview); setFile(null);
setPreview(null);
}; };
const onSubmit = async (data: AgentFormValues) => { const onSubmit = async (data: AgentFormValues) => {
const payload = {
name: data.fullName,
job_title: data.position,
phone: data.phone,
agent_type: data.roles,
profile_picture_path: data.profileImage
? data.profileImage.name
: agentData.profile_picture_path,
};
try { try {
await updateAgent(id, payload); const formData = new FormData();
formData.append("name", data.fullName);
formData.append("job_title", data.position);
formData.append("phone", data.phone);
data.roles.forEach((role) => {
formData.append("agent_type", role);
});
if (file) {
formData.append("file", file);
}
await updateAgent(id, formData);
successSubmit("/admin/agent"); successSubmit("/admin/agent");
} catch (err) { } catch (err) {
console.error("ERROR UPDATE AGENT:", err); console.error("ERROR UPDATE AGENT:", err);
@ -130,9 +139,7 @@ export default function UpdateAgentForm({ id }: { id: number }) {
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* INPUT GRID */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5"> <div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{/* Nama */}
<FormField <FormField
control={form.control} control={form.control}
name="fullName" name="fullName"
@ -217,11 +224,21 @@ export default function UpdateAgentForm({ id }: { id: number }) {
<FormLabel>Foto Agen</FormLabel> <FormLabel>Foto Agen</FormLabel>
<div className="flex items-center gap-5"> <div className="flex items-center gap-5">
{previewImg && ( {preview && (
<div className="mt-3 relative w-28 h-28 rounded-md overflow-hidden border">
<img <img
src={previewImg} src={preview}
className="w-24 h-24 rounded-lg object-cover shadow" alt="Preview"
className="w-full h-full object-cover"
/> />
<button
type="button"
onClick={handleRemoveFile}
className="absolute top-1 right-1 bg-red-600 text-white p-1 rounded-full"
>
<X className="w-4 h-4" />
</button>
</div>
)} )}
<Button <Button
@ -237,7 +254,7 @@ export default function UpdateAgentForm({ id }: { id: number }) {
type="file" type="file"
className="hidden" className="hidden"
accept="image/*" accept="image/*"
onChange={(e) => handleImageChange(e.target.files?.[0])} onChange={handleFileChange}
/> />
</div> </div>
</div> </div>

View File

@ -51,7 +51,7 @@ export function BannerDialog({
confirmButtonText: "OK", confirmButtonText: "OK",
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
router.refresh(); // ⬅️ refresh halaman router.refresh();
} }
}); });
}; };
@ -69,7 +69,7 @@ export function BannerDialog({
if (onSubmit) { if (onSubmit) {
await onSubmit(formData); await onSubmit(formData);
successSubmit(); // swal muncul disini successSubmit();
} }
onOpenChange(false); onOpenChange(false);

View File

@ -39,7 +39,7 @@ export function EditBannerDialog({
open: boolean; open: boolean;
onOpenChange: (value: boolean) => void; onOpenChange: (value: boolean) => void;
bannerData: any; bannerData: any;
onSubmit?: (data: FormData, id: number) => void; // ← fix di sini onSubmit?: (data: FormData, id: number) => void;
}) { }) {
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [file, setFile] = useState<File | null>(null); const [file, setFile] = useState<File | null>(null);
@ -49,7 +49,6 @@ export function EditBannerDialog({
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// Set data awal ketika bannerData berubah
useEffect(() => { useEffect(() => {
if (bannerData) { if (bannerData) {
setTitle(bannerData.title || ""); setTitle(bannerData.title || "");
@ -60,7 +59,10 @@ export function EditBannerDialog({
const handleFileChange = (e: any) => { const handleFileChange = (e: any) => {
const selected = e.target.files[0]; const selected = e.target.files[0];
if (selected) setFile(selected); if (selected) {
setFile(selected);
setPreview(URL.createObjectURL(selected));
}
}; };
const handleRemoveFile = () => { const handleRemoveFile = () => {
@ -85,16 +87,19 @@ export function EditBannerDialog({
const formData = new FormData(); const formData = new FormData();
formData.append("title", title); formData.append("title", title);
formData.append("position", selectedOrder.toString()); formData.append("position", selectedOrder.toString());
formData.append("description", "hardcode edit description"); formData.append("description", "dummy description");
if (file) formData.append("file", file); if (file) {
formData.append("file", file);
if (onSubmit) {
await onSubmit(formData, bannerData.id); // ← kirim ID di sini
successSubmit();
} }
try {
await onSubmit?.(formData, bannerData.id);
successSubmit();
onOpenChange(false); onOpenChange(false);
} catch (error) {
console.error("Error submit:", error);
}
}; };
const orderOptions = [ const orderOptions = [

View File

@ -19,7 +19,7 @@ import { useRouter } from "next/navigation";
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, "Nama produk wajib diisi"), name: z.string().min(1, "Nama produk wajib diisi"),
variant: z.string().min(1, "Tipe varian wajib diisi"), variant: z.string().min(1, "Tipe varian wajib diisi"),
price: z.coerce.number().min(1, "Harga produk wajib diisi"), // ⬅️ PRICE NUMBER price: z.coerce.number().min(1, "Harga produk wajib diisi"),
banner: z.instanceof(FileList).optional(), banner: z.instanceof(FileList).optional(),
}); });
@ -99,7 +99,6 @@ export default function AddProductForm() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* 3 Input Field */}
<div className="grid md:grid-cols-3 gap-4"> <div className="grid md:grid-cols-3 gap-4">
<div> <div>
<Label>Nama Produk *</Label> <Label>Nama Produk *</Label>
@ -138,7 +137,6 @@ export default function AddProductForm() {
</div> </div>
</div> </div>
{/* Upload Banner */}
<div> <div>
<Label className="text-gray-700"> <Label className="text-gray-700">
Upload Banner <span className="text-red-500">*</span> Upload Banner <span className="text-red-500">*</span>
@ -230,7 +228,6 @@ export default function AddProductForm() {
</div> </div>
))} ))}
{/* Tambah Warna Baru */}
<Button <Button
type="button" type="button"
onClick={handleAddColor} onClick={handleAddColor}
@ -247,7 +244,6 @@ export default function AddProductForm() {
{specs.map((spec, index) => ( {specs.map((spec, index) => (
<div key={spec.id} className="mt-4"> <div key={spec.id} className="mt-4">
{/* Judul Spesifikasi */}
<Label className="text-sm font-semibold"> <Label className="text-sm font-semibold">
Judul Spesifikasi {index + 1} Judul Spesifikasi {index + 1}
</Label> </Label>
@ -256,7 +252,6 @@ export default function AddProductForm() {
className="mt-1" className="mt-1"
/> />
{/* Foto Spesifikasi */}
<Label className="text-sm font-semibold mt-4 block"> <Label className="text-sm font-semibold mt-4 block">
Foto Spesifikasi {index + 1} Foto Spesifikasi {index + 1}
</Label> </Label>
@ -275,7 +270,6 @@ export default function AddProductForm() {
</div> </div>
))} ))}
{/* Tambah spesifikasi baru */}
<button <button
type="button" type="button"
onClick={handleAddSpec} onClick={handleAddSpec}

View File

@ -85,10 +85,6 @@ export default function UpdateProductForm() {
]); ]);
}; };
// ==========================================
// UPLOAD SYSTEM
// ==========================================
const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false); const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
const [uploadTarget, setUploadTarget] = useState<{ const [uploadTarget, setUploadTarget] = useState<{
type: "spec" | "color"; type: "spec" | "color";
@ -136,7 +132,6 @@ export default function UpdateProductForm() {
</CardHeader> </CardHeader>
<CardContent className="space-y-8"> <CardContent className="space-y-8">
{/* === 3 Input Field === */}
<div className="grid md:grid-cols-3 gap-4"> <div className="grid md:grid-cols-3 gap-4">
<div> <div>
<Label>Nama Produk *</Label> <Label>Nama Produk *</Label>
@ -154,13 +149,11 @@ export default function UpdateProductForm() {
</div> </div>
</div> </div>
{/* === WARNA PRODUK === */}
<div> <div>
<Label className="font-semibold">Warna Produk *</Label> <Label className="font-semibold">Warna Produk *</Label>
{colors.map((item, index) => ( {colors.map((item, index) => (
<div key={item.id} className="mt-6 border-b pb-6"> <div key={item.id} className="mt-6 border-b pb-6">
{/* Color Name */}
<Label>Pilih Warna {index + 1}</Label> <Label>Pilih Warna {index + 1}</Label>
<Input <Input
placeholder="Contoh: Silver / #E2E2E2" placeholder="Contoh: Silver / #E2E2E2"
@ -168,7 +161,6 @@ export default function UpdateProductForm() {
defaultValue={item.name} defaultValue={item.name}
/> />
{/* Palette */}
<div className="flex items-center gap-2 mt-3"> <div className="flex items-center gap-2 mt-3">
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-teal-900"> <div className="w-10 h-10 rounded-full flex items-center justify-center bg-teal-900">
<Settings className="w-5 h-5 text-white" /> <Settings className="w-5 h-5 text-white" />
@ -195,7 +187,6 @@ export default function UpdateProductForm() {
))} ))}
</div> </div>
{/* Foto Produk Warna */}
<div className="mt-4"> <div className="mt-4">
<Label className="font-semibold"> <Label className="font-semibold">
Foto Produk Warna {index + 1} Foto Produk Warna {index + 1}
@ -224,7 +215,6 @@ export default function UpdateProductForm() {
</div> </div>
))} ))}
{/* Add Color */}
<Button <Button
type="button" type="button"
onClick={handleAddColor} onClick={handleAddColor}
@ -234,7 +224,6 @@ export default function UpdateProductForm() {
</Button> </Button>
</div> </div>
{/* === SPESIFIKASI === */}
<div className="mt-10"> <div className="mt-10">
<Label className="text-lg font-semibold text-teal-900"> <Label className="text-lg font-semibold text-teal-900">
Spesifikasi Produk <span className="text-red-500">*</span> Spesifikasi Produk <span className="text-red-500">*</span>
@ -297,7 +286,6 @@ export default function UpdateProductForm() {
</CardContent> </CardContent>
</Card> </Card>
{/* ===== Dialog Upload File ===== */}
<Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}> <Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
<DialogHeader> <DialogHeader>

View File

@ -16,14 +16,14 @@ export default function AddPromoForm() {
const router = useRouter(); const router = useRouter();
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [file, setFile] = useState<File | null>(null); // ⬅️ TAMBAH FILE STATE const [file, setFile] = useState<File | null>(null);
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selected = e.target.files?.[0]; const selected = e.target.files?.[0];
if (selected) { if (selected) {
setFile(selected); // ⬅️ SET FILE setFile(selected);
} }
}; };
@ -36,12 +36,11 @@ export default function AddPromoForm() {
} }
try { try {
// ⬅️ GUNAKAN FORMDATA
const formData = new FormData(); const formData = new FormData();
formData.append("title", title); formData.append("title", title);
formData.append("description", title); // sementara sama dulu formData.append("description", title);
formData.append("file", file); // ⬅️ FILE TERKIRIM formData.append("file", file);
await createPromotion(formData); await createPromotion(formData);
@ -74,7 +73,6 @@ export default function AddPromoForm() {
<CardContent> <CardContent>
<form className="space-y-6" onSubmit={onSubmit}> <form className="space-y-6" onSubmit={onSubmit}>
{/* Judul Promo */}
<div> <div>
<Label className="text-sm font-semibold"> <Label className="text-sm font-semibold">
Judul Promo <span className="text-red-500">*</span> Judul Promo <span className="text-red-500">*</span>
@ -87,7 +85,6 @@ export default function AddPromoForm() {
/> />
</div> </div>
{/* Upload File */}
<div> <div>
<Label className="text-sm font-semibold">Upload File *</Label> <Label className="text-sm font-semibold">Upload File *</Label>
@ -98,7 +95,6 @@ export default function AddPromoForm() {
</p> </p>
<p className="text-xs text-gray-500 mt-1">PNG, JPG (max 2 MB)</p> <p className="text-xs text-gray-500 mt-1">PNG, JPG (max 2 MB)</p>
{/* ⬅️ Tambahkan onChange disini */}
<input <input
type="file" type="file"
accept="image/png,image/jpeg" accept="image/png,image/jpeg"
@ -107,7 +103,6 @@ export default function AddPromoForm() {
/> />
</label> </label>
{/* Nama file muncul setelah dipilih */}
{file && ( {file && (
<p className="text-xs text-teal-800 mt-2 font-medium"> <p className="text-xs text-teal-800 mt-2 font-medium">
{file.name} {file.name}
@ -115,7 +110,6 @@ export default function AddPromoForm() {
)} )}
</div> </div>
{/* Tombol Submit */}
<Button <Button
type="submit" type="submit"
className="bg-teal-800 hover:bg-teal-900 text-white px-8 py-6 text-lg mt-4" className="bg-teal-800 hover:bg-teal-900 text-white px-8 py-6 text-lg mt-4"

View File

@ -3,7 +3,12 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { Eye, Pencil, Trash2, Calendar, MapPin } from "lucide-react"; import { Eye, Pencil, Trash2, Calendar, MapPin } from "lucide-react";
import { deleteGalery, getGaleryById, getGaleryData } from "@/service/galery"; import {
deleteGalery,
getGaleryById,
getGaleryData,
getGaleryFileData,
} from "@/service/galery";
import { DialogDetailGaleri } from "../dialog/galery-detail-dialog"; import { DialogDetailGaleri } from "../dialog/galery-detail-dialog";
import { DialogUpdateGaleri } from "../dialog/galery-update-dialog"; import { DialogUpdateGaleri } from "../dialog/galery-update-dialog";
import { error, success } from "@/config/swal"; import { error, success } from "@/config/swal";
@ -12,7 +17,8 @@ import Swal from "sweetalert2";
export default function Galery() { export default function Galery() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const [data, setData] = useState([]); const [data, setData] = useState<any[]>([]);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [showData, setShowData] = useState("10"); const [showData, setShowData] = useState("10");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@ -30,10 +36,35 @@ export default function Galery() {
}; };
const res = await getGaleryData(req); const res = await getGaleryData(req);
const list = res?.data?.data ?? [];
// Pastikan respons API sesuai bentuknya // Ambil gambar yang sesuai gallery_id dan gabungkan ke data galeri
// Misal: { data: [...], total: number } const merged = await Promise.all(
setData(res?.data?.data); list.map(async (item: any) => {
try {
const filesRes = await getGaleryFileData(item.id);
const images = filesRes?.data?.data ?? [];
// Filter file dengan gallery_id sama dengan item.id
const filteredImages = images?.filter(
(img: any) => img.gallery_id === item.id
);
// Ambil gambar pertama sebagai thumbnail
const coverImage =
filteredImages.length > 0 ? filteredImages[0].image_url : null;
return {
...item,
image_url: coverImage,
};
} catch (e) {
return { ...item, image_url: null };
}
})
);
setData(merged);
} catch (error) { } catch (error) {
console.error("Error fetch galeri:", error); console.error("Error fetch galeri:", error);
} }
@ -64,14 +95,12 @@ export default function Galery() {
}; };
async function doDelete(id: any) { async function doDelete(id: any) {
// loading();
const resDelete = await deleteGalery(id); const resDelete = await deleteGalery(id);
if (resDelete?.error) { if (resDelete?.error) {
error(resDelete.message); error(resDelete.message);
return false; return false;
} }
close();
success("Berhasil Hapus"); success("Berhasil Hapus");
fetchData(); fetchData();
} }
@ -93,7 +122,6 @@ export default function Galery() {
return ( return (
<div className="mt-6"> <div className="mt-6">
{/* Card Wrapper */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{data?.map((item: any) => ( {data?.map((item: any) => (
<div <div
@ -102,12 +130,18 @@ export default function Galery() {
> >
{/* Image */} {/* Image */}
<div className="relative w-full h-48"> <div className="relative w-full h-48">
{item.image_url ? (
<Image <Image
src={item.image_url} src={item.image_url}
alt={item.title} alt={item.title}
fill fill
className="object-cover" className="object-cover"
/> />
) : (
<div className="flex items-center justify-center h-full bg-gray-100 text-gray-400">
No Image
</div>
)}
{/* Status Badge */} {/* Status Badge */}
<span <span
@ -127,14 +161,13 @@ export default function Galery() {
<h2 className="text-[#1F6779] text-lg font-semibold"> <h2 className="text-[#1F6779] text-lg font-semibold">
{item.title} {item.title}
</h2> </h2>
<p className="text-gray-600 text-sm">{item.desc ?? "-"}</p> <p className="text-gray-600 text-sm">{item.description ?? "-"}</p>
{/* Date */}
<div className="flex items-center gap-2 text-sm text-gray-700"> <div className="flex items-center gap-2 text-sm text-gray-700">
<Calendar className="h-4 w-4" />{" "} <Calendar className="h-4 w-4" />{" "}
{new Date(item.created_at).toLocaleDateString("id-ID")} {new Date(item.created_at).toLocaleDateString("id-ID")}
</div> </div>
{/* Location (jika tidak ada, tampilkan strip) */}
<div className="flex items-center gap-2 text-sm text-gray-700"> <div className="flex items-center gap-2 text-sm text-gray-700">
<MapPin className="h-4 w-4" /> {item.location ?? "-"} <MapPin className="h-4 w-4" /> {item.location ?? "-"}
</div> </div>
@ -188,6 +221,7 @@ export default function Galery() {
</button> </button>
</div> </div>
</div> </div>
{showDetail && detailData && ( {showDetail && detailData && (
<DialogDetailGaleri <DialogDetailGaleri
open={showDetail} open={showDetail}
@ -195,12 +229,13 @@ export default function Galery() {
data={detailData} data={detailData}
/> />
)} )}
{showEdit && editData && ( {showEdit && editData && (
<DialogUpdateGaleri <DialogUpdateGaleri
open={showEdit} open={showEdit}
onClose={() => setShowEdit(false)} onClose={() => setShowEdit(false)}
data={editData} data={editData}
onUpdated={fetchData} // refresh setelah update onUpdated={fetchData}
/> />
)} )}
</div> </div>

View File

@ -20,13 +20,17 @@ export async function getAgentById(id: any) {
} }
export async function createAgent(data: any) { export async function createAgent(data: any) {
const pathUrl = `/sales-agents`; const headers = {
return await httpPostInterceptor(pathUrl, data); "content-type": "multipart/form-data",
};
return await httpPostInterceptor(`/sales-agents`, data, headers);
} }
export async function updateAgent(id: number, data: any) { export async function updateAgent(id: number, data: any) {
const pathUrl = `/sales-agents/${id}`; const headers = {
return await httpPutInterceptor(pathUrl, data); "content-type": "multipart/form-data",
};
return await httpPutInterceptor(`/sales-agents/${id}`, data, headers);
} }
export async function deleteAgent(id: string) { export async function deleteAgent(id: string) {

View File

@ -32,10 +32,17 @@ export async function createBanner(data: any) {
} }
export async function updateBanner(data: any, id: any) { export async function updateBanner(data: any, id: any) {
const pathUrl = `/banners/${id}`; const headers = {
return await httpPutInterceptor(pathUrl, data); "content-type": "multipart/form-data",
};
return await httpPutInterceptor(`/banners/${id}`, data, headers);
} }
// export async function updateBanner(data: any, id: any) {
// const pathUrl = `/banners/${id}`;
// return await httpPutInterceptor(pathUrl, data);
// }
export async function deleteBanner(id: string) { export async function deleteBanner(id: string) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",

View File

@ -8,6 +8,11 @@ import {
import { httpGet } from "./http-config/http-base-services"; import { httpGet } from "./http-config/http-base-services";
export async function getGaleryData(props: PaginationRequest) { export async function getGaleryData(props: PaginationRequest) {
const { page, limit, search } = props;
return await httpGetInterceptor(`/galleries`);
}
export async function getGaleryFileData(props: PaginationRequest) {
const { page, limit, search } = props; const { page, limit, search } = props;
return await httpGetInterceptor(`/gallery-files`); return await httpGetInterceptor(`/gallery-files`);
} }
@ -16,31 +21,45 @@ export async function getGaleryById(id: any) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpGet(`/gallery-files/${id}`, headers); return await httpGet(`/galleries/${id}`, headers);
}
export async function getGaleryFileName(filename: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/gallery-files/viewer/${filename}`, headers);
} }
export async function createGalery(data: any) { export async function createGalery(data: any) {
const headers = { const pathUrl = `/galleries`;
"content-type": "multipart/form-data", return await httpPostInterceptor(pathUrl, data);
};
return await httpPostInterceptor(`/gallery-files`, data, headers);
} }
export async function updateGalery(id: any, data: any) { export async function updateGalery(id: any, data: any) {
const headers = {
"content-type": "multipart/form-data",
};
return await httpPutInterceptor(`/galleries/${id}`, data, headers);
}
export async function updateUploadGaleryFile(id: any, data: any) {
const headers = { const headers = {
"content-type": "multipart/form-data", "content-type": "multipart/form-data",
}; };
return await httpPutInterceptor(`/gallery-files/${id}`, data, headers); return await httpPutInterceptor(`/gallery-files/${id}`, data, headers);
} }
// export async function updateGalery(data: any, id: any) { export async function uploadGaleryFile(id: any, data: any) {
// const pathUrl = `/gallery-files/${id}`; const headers = {
// return await httpPutInterceptor(pathUrl, data); "content-type": "multipart/form-data",
// } };
return await httpPostInterceptor(`/gallery-files/`, data, headers);
}
export async function deleteGalery(id: string) { export async function deleteGalery(id: string) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpDeleteInterceptor(`gallery-files/${id}`, headers); return await httpDeleteInterceptor(`galleries/${id}`, headers);
} }