fix: table agent,banner,product,promo,galery, form and dialog

This commit is contained in:
Anang Yusman 2026-01-20 17:22:45 +08:00
commit f516565446
20 changed files with 629 additions and 145 deletions

View File

@ -16,15 +16,15 @@ import { useRouter } from "next/navigation";
export default function AgentPage() { export default function AgentPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
@ -41,7 +41,7 @@ export default function AgentPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Link href={"/admin/agent/create"}> <Link href={"/admin/agent/create"}>
<Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"> <Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2">
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />

View File

@ -15,15 +15,15 @@ import Cookies from "js-cookie";
export default function BasicPage() { export default function BasicPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [refreshKey, setRefreshKey] = useState(0); const [refreshKey, setRefreshKey] = useState(0);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const ulne = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(ulne ?? null);
}, []); }, []);
const handleSubmitBanner = async (formData: FormData) => { const handleSubmitBanner = async (formData: FormData) => {
@ -58,7 +58,7 @@ export default function BasicPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<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)}

View File

@ -13,15 +13,15 @@ import Cookies from "js-cookie";
export default function GaleryPage() { export default function GaleryPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
const handleSubmitGaleri = () => { const handleSubmitGaleri = () => {
@ -39,7 +39,7 @@ export default function GaleryPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<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)}

View File

@ -14,15 +14,15 @@ import Cookies from "js-cookie";
export default function ProductPage() { export default function ProductPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
@ -40,7 +40,7 @@ export default function ProductPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Link href={"/admin/product/create"}> <Link href={"/admin/product/create"}>
<Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"> <Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2">
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />

View File

@ -17,15 +17,15 @@ import Cookies from "js-cookie";
export default function PromotionPage() { export default function PromotionPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
@ -43,7 +43,7 @@ export default function PromotionPage() {
</div> </div>
<div className="dark:bg-[#18181b] rounded-xl p-3"> <div className="dark:bg-[#18181b] rounded-xl p-3">
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Link href={"/admin/promotion/create"}> <Link href={"/admin/promotion/create"}>
<Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"> <Button className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2">
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />

View File

@ -10,24 +10,30 @@ import {
import Image from "next/image"; import Image from "next/image";
import { Check, CheckCheck, CheckCircle, Clock, X } from "lucide-react"; import { Check, CheckCheck, CheckCircle, Clock, X } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { approveGalery, getGaleryFileData } from "@/service/galery"; import {
approveGalery,
getGaleryFileData,
rejectGalery,
} from "@/service/galery";
import { convertDateFormat } from "@/utils/global"; import { convertDateFormat } from "@/utils/global";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { error, loading, success } from "@/config/swal"; import { error, loading, success } from "@/config/swal";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
export function DialogDetailGaleri({ open, onClose, data }: any) { export function DialogDetailGaleri({ open, onClose, data }: any) {
const [images, setImages] = useState<any[]>([]); const [images, setImages] = useState<any[]>([]);
const [openApproverHistory, setOpenApproverHistory] = useState(false); const [openApproverHistory, setOpenApproverHistory] = useState(false);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const [openViewDialog, setOpenViewDialog] = useState(false); const [openViewDialog, setOpenViewDialog] = useState(false);
const router = useRouter(); const router = useRouter();
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userlevelId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // contoh: "3"
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
const [openCommentModal, setOpenCommentModal] = useState(false); const [openCommentModal, setOpenCommentModal] = useState(false);
@ -84,6 +90,40 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
fetchImages(); // refresh table fetchImages(); // refresh table
}; };
const handleRejectGalery = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Galery",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectGalery(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak galery");
close();
return;
}
close();
success("Galery berhasil ditolak");
fetchImages(); // refresh table
};
return ( return (
<> <>
<Dialog open={open} onOpenChange={onClose}> <Dialog open={open} onOpenChange={onClose}>
@ -214,7 +254,7 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
</div> </div>
</div> </div>
</div> </div>
{openViewDialog && userLevelId !== "2" && ( {userRoleId !== "2" && data && (
<div className="flex justify-between items-center gap-3 px-6 py-4 border-t bg-[#F2F7FA]"> <div className="flex justify-between items-center gap-3 px-6 py-4 border-t bg-[#F2F7FA]">
{data.status_id === 1 ? ( {data.status_id === 1 ? (
<> <>
@ -232,15 +272,15 @@ export function DialogDetailGaleri({ open, onClose, data }: any) {
<Button <Button
className=" w-[180]" className=" w-[180]"
variant="destructive" variant="destructive"
// onClick={(e) => { onClick={(e) => {
// e.stopPropagation(); e.stopPropagation();
// handleReject(); handleRejectGalery(data.id);
// }} }}
> >
Reject Reject
</Button> </Button>
{userLevelId === "1" && ( {userRoleId === "1" && (
<Button <Button
// variant="ghost" // variant="ghost"
size="sm" size="sm"

View File

@ -15,12 +15,18 @@ import {
FileText, FileText,
X, X,
} from "lucide-react"; } from "lucide-react";
import { approvePromotion, getPromotionById } from "@/service/promotion"; import {
approvePromotion,
getPromotionById,
rejectPromotion,
} from "@/service/promotion";
import { convertDateFormat } from "@/utils/global"; import { convertDateFormat } from "@/utils/global";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { error, loading, success } from "@/config/swal"; import { error, loading, success } from "@/config/swal";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
type PromoDetailDialogProps = { type PromoDetailDialogProps = {
promoId: number | null; promoId: number | null;
@ -60,6 +66,40 @@ export default function PromoDetailDialog({
setOpenApproverHistory(true); setOpenApproverHistory(true);
}; };
const handleRejectPromotion = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Promotion",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectPromotion(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak promotion");
close();
return;
}
close();
success("Promotion berhasil ditolak");
// initState(); // refresh table
};
const handleApprovePromotion = async (promoId: number) => { const handleApprovePromotion = async (promoId: number) => {
loading(); loading();
const res = await approvePromotion(promoId); const res = await approvePromotion(promoId);
@ -280,10 +320,10 @@ export default function PromoDetailDialog({
<Button <Button
className=" w-[150]" className=" w-[150]"
variant="destructive" variant="destructive"
// onClick={(e) => { onClick={(e) => {
// e.stopPropagation(); e.stopPropagation();
// handleReject(); handleRejectPromotion(promo.id);
// }} }}
> >
Reject Reject
</Button> </Button>
@ -319,7 +359,7 @@ export default function PromoDetailDialog({
)} )}
{openApproverHistory && ( {openApproverHistory && (
<div <div
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4" className=" flex items-center justify-center bg-black/50 p-4"
onClick={() => setOpenApproverHistory(false)} onClick={() => setOpenApproverHistory(false)}
> >
<div <div

View File

@ -31,9 +31,16 @@ import dynamic from "next/dynamic";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { getProductDataById } from "@/service/product"; import { getProductDataById } from "@/service/product";
import { approveAgent, getAgentById, updateAgent } from "@/service/agent"; import {
approveAgent,
getAgentById,
rejectAgent,
updateAgent,
} from "@/service/agent";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { convertDateFormat } from "@/utils/global"; import { convertDateFormat } from "@/utils/global";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
const ViewEditor = dynamic( const ViewEditor = dynamic(
() => { () => {
@ -128,25 +135,39 @@ export default function DetailAgentForm(props: { isDetail: boolean }) {
setOpenApproverHistory(true); setOpenApproverHistory(true);
}; };
// const handleRejectProduct = async (id: number) => { const handleRejectAgent = async (id: number) => {
// loading(); const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Agent",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
// const payload = { if (message === undefined) {
// status_id: 3, // ✅ number return; // User cancelled
// }; }
// const res = await updateAgent(payload, id); loading();
const res = await rejectAgent(id, message || undefined);
// if (res?.error) { if (res?.error) {
// error(res.message || "Gagal menolak product"); error(res.message || "Gagal menolak agent");
// close(); close();
// return; return;
// } }
// close(); close();
// success("Product berhasil ditolak"); success("Agent berhasil ditolak");
// fetchData(); fetchData(); // refresh table
// }; };
const handleApproveAgent = async (id: number) => { const handleApproveAgent = async (id: number) => {
loading(); loading();
@ -298,7 +319,7 @@ export default function DetailAgentForm(props: { isDetail: boolean }) {
<Button <Button
variant="destructive" variant="destructive"
className="w-[180px]" className="w-[180px]"
// onClick={() => handleRejectProduct(detailData.id)} onClick={() => handleRejectAgent(data.id)}
> >
Reject Reject
</Button> </Button>

View File

@ -33,9 +33,12 @@ import Cookies from "js-cookie";
import { import {
approveProduct, approveProduct,
getProductDataById, getProductDataById,
rejectProduct,
updateProduct, updateProduct,
} from "@/service/product"; } from "@/service/product";
import { convertDateFormat } from "@/utils/global"; import { convertDateFormat } from "@/utils/global";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
const ViewEditor = dynamic( const ViewEditor = dynamic(
() => { () => {
@ -302,13 +305,27 @@ export default function DetailProductForm(props: { isDetail: boolean }) {
} }
const handleRejectProduct = async (id: number) => { const handleRejectProduct = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Product",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading(); loading();
const res = await rejectProduct(id, message || undefined);
const payload = {
status_id: 3, // ✅ number
};
const res = await updateProduct(payload, id);
if (res?.error) { if (res?.error) {
error(res.message || "Gagal menolak product"); error(res.message || "Gagal menolak product");
@ -318,7 +335,7 @@ export default function DetailProductForm(props: { isDetail: boolean }) {
close(); close();
success("Product berhasil ditolak"); success("Product berhasil ditolak");
initState(); initState(); // refresh table
}; };
const handleApproveProduct = async (id: number) => { const handleApproveProduct = async (id: number) => {

View File

@ -6,12 +6,15 @@ import Image from "next/image";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import Link from "next/link"; import Link from "next/link";
import DashboardContainer from "../main/dashboard/dashboard-container"; import DashboardContainer from "../main/dashboard/dashboard-container";
import { usePathname } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { useTheme } from "../layout/theme-context"; import { useTheme } from "../layout/theme-context";
import Option from "./option"; import Option from "./option";
import { LogOut } from "lucide-react"; import { LogOut } from "lucide-react";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
interface RetractingSidebarProps { interface RetractingSidebarProps {
sidebarData: boolean; sidebarData: boolean;
@ -87,6 +90,7 @@ const sidebarSections = [
), ),
link: "/admin/costumer-service", link: "/admin/costumer-service",
}, },
{ {
title: "Manajemen User", title: "Manajemen User",
icon: () => <Icon icon="mdi:contact" width="18" height="18" />, icon: () => <Icon icon="mdi:contact" width="18" height="18" />,
@ -206,6 +210,18 @@ const SidebarContent = ({
pathname: string; pathname: string;
updateSidebarData: (newData: boolean) => void; updateSidebarData: (newData: boolean) => void;
}) => { }) => {
const roleLabel: Record<number, string> = {
1: "Admin",
2: "Operator",
};
const [userRoleId, setUserRoleId] = useState<number | null>(null);
useEffect(() => {
const urie = Cookies.get("urie");
setUserRoleId(urie ? Number(urie) : null);
}, []);
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
@ -228,7 +244,10 @@ const SidebarContent = ({
className="flex flex-row" className="flex flex-row"
> >
<p className="text-lg font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-white dark:text-white"> <p className="text-lg font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-white dark:text-white">
JAECOO <span className="text-lg font-normal">Admin</span> JAECOO{" "}
<span className="text-lg font-normal">
{userRoleId != null ? roleLabel[userRoleId] : ""}
</span>
</p> </p>
{/* <span className="text-xs text-slate-500">Admin Panel</span> */} {/* <span className="text-xs text-slate-500">Admin Panel</span> */}
</motion.div> </motion.div>
@ -540,7 +559,7 @@ const Sidebar = () => {
</g> </g>
</svg> </svg>
<div className="flex flex-col gap-0.5 text-xs"> <div className="flex flex-col gap-0.5 text-xs">
<p>admin-mabes</p> {/* <p>admin-mabes</p> */}
<p className="underline">Logout</p> <p className="underline">Logout</p>
</div> </div>
</div> </div>

View File

@ -47,9 +47,14 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import CustomPagination from "../layout/custom-pagination"; import CustomPagination from "../layout/custom-pagination";
import { EditBannerDialog } from "../form/banner-edit-dialog"; import { EditBannerDialog } from "../form/banner-edit-dialog";
import { Eye, CheckCheck } from "lucide-react"; import { Eye, CheckCheck, X } from "lucide-react";
import AgentDetailDialog from "../dialog/agent-dialog"; import AgentDetailDialog from "../dialog/agent-dialog";
import { deleteAgent, getAgentData, approveAgent } from "@/service/agent"; import {
deleteAgent,
getAgentData,
approveAgent,
rejectAgent,
} from "@/service/agent";
const columns = [ const columns = [
{ name: "No", uid: "no" }, { name: "No", uid: "no" },
@ -94,12 +99,12 @@ export default function AgentTable() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userRoleId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // userRoleId dari cookies
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -202,6 +207,40 @@ export default function AgentTable() {
initState(); // refresh table initState(); // refresh table
}; };
const handleRejectAgent = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Agent",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectAgent(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak agent");
close();
return;
}
close();
success("Agent berhasil ditolak");
initState(); // refresh table
};
const copyUrlArticle = async (id: number, slug: string) => { const copyUrlArticle = async (id: number, slug: string) => {
const url = const url =
`${window.location.protocol}//${window.location.host}` + `${window.location.protocol}//${window.location.host}` +
@ -452,7 +491,7 @@ export default function AgentTable() {
</Button> </Button>
</Link> </Link>
{/* Tombol Edit */} {/* Tombol Edit */}
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Link href={`/admin/agent/update/${item.id}`}> <Link href={`/admin/agent/update/${item.id}`}>
<Button <Button
className="text-[#0F6C75] hover:bg-transparent hover:underline p-0" className="text-[#0F6C75] hover:bg-transparent hover:underline p-0"

View File

@ -51,9 +51,12 @@ import {
getBannerData, getBannerData,
updateBanner, updateBanner,
approveBanner, approveBanner,
rejectBanner,
getApprovalHistory,
} from "@/service/banner"; } from "@/service/banner";
import { Check, CheckCheck, Clock, Eye, X } from "lucide-react"; import { Check, CheckCheck, Clock, Eye, X } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import page from "@/app/page";
const columns = [ const columns = [
{ name: "No", uid: "no" }, { name: "No", uid: "no" },
@ -96,14 +99,14 @@ export default function ArticleTable() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userRoleId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // userRoleId dari cookies
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -259,6 +262,39 @@ export default function ArticleTable() {
initState(); // refresh table initState(); // refresh table
}; };
const handleRejectBanner = async (id: number) => {
const { value: message } = await MySwal.fire({
title: "Tolak Banner",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectBanner(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak banner");
close();
return;
}
close();
success("Banner berhasil ditolak");
initState(); // refresh table
};
const handleReject = async () => { const handleReject = async () => {
if (!viewBanner) return; if (!viewBanner) return;
@ -514,7 +550,7 @@ export default function ArticleTable() {
<Eye className="w-4 h-4 mr-1" /> <Eye className="w-4 h-4 mr-1" />
Lihat Lihat
</Button> </Button>
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -525,7 +561,8 @@ export default function ArticleTable() {
Edit Edit
</Button> </Button>
)} )}
{/* {userLevelId === "1" && item.status_id === 1 && ( {/* {userRoleId === "1" && item.status_id === 1 && (
<>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -535,6 +572,16 @@ export default function ArticleTable() {
<CheckCheck className="w-4 h-4 mr-1" /> <CheckCheck className="w-4 h-4 mr-1" />
Approve Approve
</Button> </Button>
<Button
variant="ghost"
size="sm"
className="text-red-600 hover:bg-transparent hover:underline p-0"
onClick={() => handleRejectBanner(item.id)}
>
<X className="w-4 h-4 mr-1" />
Reject
</Button>
</>
)} */} )} */}
<Button <Button
variant="ghost" variant="ghost"
@ -810,7 +857,7 @@ export default function ArticleTable() {
</div> </div>
{/* FOOTER */} {/* FOOTER */}
{userLevelId !== "2" && ( {userRoleId !== "2" && (
<div className="flex justify-between items-center gap-3 px-6 py-4 border-t bg-[#F2F7FA]"> <div className="flex justify-between items-center gap-3 px-6 py-4 border-t bg-[#F2F7FA]">
{viewBanner.status_id === 1 ? ( {viewBanner.status_id === 1 ? (
<> <>
@ -830,13 +877,13 @@ export default function ArticleTable() {
variant="destructive" variant="destructive"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleReject(); handleRejectBanner(viewBanner.id);
}} }}
> >
Reject Reject
</Button> </Button>
{userLevelId === "1" && ( {userRoleId === "1" && (
<Button <Button
// variant="ghost" // variant="ghost"
size="sm" size="sm"

View File

@ -9,6 +9,7 @@ import {
Calendar, Calendar,
MapPin, MapPin,
CheckCheck, CheckCheck,
X,
} from "lucide-react"; } from "lucide-react";
import { import {
deleteGalery, deleteGalery,
@ -16,6 +17,7 @@ import {
getGaleryData, getGaleryData,
getGaleryFileData, getGaleryFileData,
approveGalery, approveGalery,
rejectGalery,
} from "@/service/galery"; } 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";
@ -23,11 +25,12 @@ import { error, success, loading, close } from "@/config/swal";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { Badge } from "../ui/badge";
export default function Galery() { export default function Galery() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [showData, setShowData] = useState("10"); const [showData, setShowData] = useState("10");
@ -80,10 +83,10 @@ export default function Galery() {
} }
}; };
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userRoleId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // userRoleId dari cookies
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -151,6 +154,39 @@ export default function Galery() {
fetchData(); // refresh table fetchData(); // refresh table
}; };
const handleRejectGalery = async (id: number) => {
const { value: message } = await MySwal.fire({
title: "Tolak Galery",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectGalery(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak galery");
close();
return;
}
close();
success("Galery berhasil ditolak");
fetchData(); // refresh table
};
return ( return (
<div className="mt-6"> <div className="mt-6">
<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">
@ -175,13 +211,14 @@ export default function Galery() {
)} )}
{/* Status Badge */} {/* Status Badge */}
<span <Badge
className={`absolute top-3 left-3 text-xs px-3 py-1 rounded-full font-medium className={`absolute top-3 left-3 px-3 py-1 text-xs font-medium ${
${
item.status_id === 2 item.status_id === 2
? "bg-green-100 text-green-700" ? "bg-green-100 text-green-700"
: item.status_id === 1 : item.status_id === 1
? "bg-yellow-100 text-yellow-700" ? "bg-yellow-100 text-yellow-700"
: item.status_id === 3
? "bg-red-100 text-red-700"
: "bg-gray-100 text-gray-600" : "bg-gray-100 text-gray-600"
}`} }`}
> >
@ -189,8 +226,8 @@ export default function Galery() {
? "Disetujui" ? "Disetujui"
: item.status_id === 1 : item.status_id === 1
? "Menunggu" ? "Menunggu"
: "Tidak Diketahui"} : "Ditolak"}
</span> </Badge>
</div> </div>
{/* Content */} {/* Content */}
@ -218,7 +255,7 @@ export default function Galery() {
> >
<Eye className="h-4 w-4" /> Lihat <Eye className="h-4 w-4" /> Lihat
</button> </button>
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<button <button
onClick={() => openEdit(item.id)} onClick={() => openEdit(item.id)}
className="flex items-center gap-1 text-gray-700 hover:text-[#1F6779] transition" className="flex items-center gap-1 text-gray-700 hover:text-[#1F6779] transition"
@ -226,14 +263,22 @@ export default function Galery() {
<Pencil className="h-4 w-4" /> Edit <Pencil className="h-4 w-4" /> Edit
</button> </button>
)} )}
{/* Tombol Approve - hanya untuk admin dan status pending */} {/* Tombol Approve & Reject - hanya untuk admin dan status pending */}
{/* {userLevelId === "1" && item.status_id === 1 && ( {/* {userRoleId === "1" && item.status_id === 1 && (
<>
<button <button
className="flex items-center gap-1 text-green-600 hover:text-green-700 transition" className="flex items-center gap-1 text-green-600 hover:text-green-700 transition"
onClick={() => handleApproveGalery(item.id)} onClick={() => handleApproveGalery(item.id)}
> >
<CheckCheck className="h-4 w-4" /> Approve <CheckCheck className="h-4 w-4" /> Approve
</button> </button>
<button
className="flex items-center gap-1 text-red-600 hover:text-red-700 transition"
onClick={() => handleRejectGalery(item.id)}
>
<X className="h-4 w-4" /> Reject
</button>
</>
)} */} )} */}
<button <button

View File

@ -51,8 +51,9 @@ import {
deleteProduct, deleteProduct,
getProductPagination, getProductPagination,
approveProduct, approveProduct,
rejectProduct,
} from "@/service/product"; } from "@/service/product";
import { CheckCheck, Eye } from "lucide-react"; import { CheckCheck, Eye, X } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
const columns = [ const columns = [
@ -96,14 +97,14 @@ export default function ProductTable() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userRoleId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // userRoleId dari cookies
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -213,6 +214,40 @@ export default function ProductTable() {
initState(); // refresh table initState(); // refresh table
}; };
const handleRejectProduct = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Product",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectProduct(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak product");
close();
return;
}
close();
success("Product berhasil ditolak");
initState(); // refresh table
};
const copyUrlArticle = async (id: number, slug: string) => { const copyUrlArticle = async (id: number, slug: string) => {
const url = const url =
`${window.location.protocol}//${window.location.host}` + `${window.location.protocol}//${window.location.host}` +
@ -476,7 +511,7 @@ export default function ProductTable() {
Lihat Lihat
</Button> </Button>
</Link> </Link>
{userLevelId !== "1" && ( {userRoleId !== "1" && (
<Link href={"/admin/product/update"}> <Link href={"/admin/product/update"}>
<Button <Button
className="text-[#0F6C75] hover:bg-transparent hover:underline p-0" className="text-[#0F6C75] hover:bg-transparent hover:underline p-0"
@ -487,7 +522,8 @@ export default function ProductTable() {
</Button> </Button>
</Link> </Link>
)} )}
{/* {userLevelId === "1" && item.status_id === 1 && ( {userRoleId === "1" && item.status_id === 1 && (
<>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -497,7 +533,17 @@ export default function ProductTable() {
<CheckCheck className="w-4 h-4 mr-1" /> <CheckCheck className="w-4 h-4 mr-1" />
Approve Approve
</Button> </Button>
)} */} <Button
variant="ghost"
size="sm"
onClick={() => handleRejectProduct(item.id)}
className="text-red-600 hover:bg-transparent hover:underline p-0"
>
<X className="w-4 h-4 mr-1" />
Reject
</Button>
</>
)}
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"

View File

@ -47,13 +47,14 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import CustomPagination from "../layout/custom-pagination"; import CustomPagination from "../layout/custom-pagination";
import { EditBannerDialog } from "../form/banner-edit-dialog"; import { EditBannerDialog } from "../form/banner-edit-dialog";
import { Eye, Trash2, CheckCheck } from "lucide-react"; import { Eye, Trash2, CheckCheck, X } from "lucide-react";
import AgentDetailDialog from "../dialog/agent-dialog"; import AgentDetailDialog from "../dialog/agent-dialog";
import PromoDetailDialog from "../dialog/promo-dialog"; import PromoDetailDialog from "../dialog/promo-dialog";
import { import {
deletePromotion, deletePromotion,
getPromotionPagination, getPromotionPagination,
approvePromotion, approvePromotion,
rejectPromotion,
} from "@/service/promotion"; } from "@/service/promotion";
const columns = [ const columns = [
@ -100,12 +101,12 @@ export default function PromotionTable() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [userLevelId, setUserLevelId] = useState<string | null>(null); const [userRoleId, setUserRoleId] = useState<string | null>(null);
// 🔹 Ambil userlevelId dari cookies // 🔹 Ambil userRoleId dari cookies
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3" const urie = Cookies.get("urie"); // userRoleId dari cookies
setUserLevelId(ulne ?? null); setUserRoleId(urie ?? null);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -208,6 +209,40 @@ export default function PromotionTable() {
initState(); // refresh table initState(); // refresh table
}; };
const handleRejectPromotion = async (id: number) => {
const MySwal = withReactContent(Swal);
const { value: message } = await MySwal.fire({
title: "Tolak Promotion",
input: "textarea",
inputLabel: "Alasan penolakan (opsional)",
inputPlaceholder: "Masukkan alasan penolakan...",
inputAttributes: {
"aria-label": "Masukkan alasan penolakan",
},
showCancelButton: true,
confirmButtonText: "Tolak",
cancelButtonText: "Batal",
confirmButtonColor: "#dc2626",
});
if (message === undefined) {
return; // User cancelled
}
loading();
const res = await rejectPromotion(id, message || undefined);
if (res?.error) {
error(res.message || "Gagal menolak promotion");
close();
return;
}
close();
success("Promotion berhasil ditolak");
initState(); // refresh table
};
const copyUrlArticle = async (id: number, slug: string) => { const copyUrlArticle = async (id: number, slug: string) => {
const url = const url =
`${window.location.protocol}//${window.location.host}` + `${window.location.protocol}//${window.location.host}` +
@ -412,6 +447,10 @@ export default function PromotionTable() {
<span className="bg-green-100 text-green-700 text-xs px-3 py-1 rounded-full font-medium"> <span className="bg-green-100 text-green-700 text-xs px-3 py-1 rounded-full font-medium">
Disetujui Disetujui
</span> </span>
) : item.status_id === 3 ? (
<span className="bg-red-100 text-red-700 text-xs px-3 py-1 rounded-full font-medium">
Ditolak
</span>
) : ( ) : (
<span className="bg-gray-100 text-gray-600 text-xs px-3 py-1 rounded-full font-medium"> <span className="bg-gray-100 text-gray-600 text-xs px-3 py-1 rounded-full font-medium">
{item.status_id || "Tidak Diketahui"} {item.status_id || "Tidak Diketahui"}
@ -436,8 +475,9 @@ export default function PromotionTable() {
</Button> </Button>
{/* Tombol Edit */} {/* Tombol Edit */}
{/* Tombol Approve - hanya untuk admin dan status pending */} {/* Tombol Approve & Reject - hanya untuk admin dan status pending */}
{/* {userLevelId === "1" && item.status_id === 1 && ( {/* {userRoleId === "1" && item.status_id === 1 && (
<>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -447,6 +487,16 @@ export default function PromotionTable() {
<CheckCheck className="w-4 h-4 mr-1" /> <CheckCheck className="w-4 h-4 mr-1" />
Approve Approve
</Button> </Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRejectPromotion(item.id)}
className="text-red-600 hover:bg-transparent hover:underline p-0"
>
<X className="w-4 h-4 mr-1" />
Reject
</Button>
</>
)} */} )} */}
{/* Tombol Hapus */} {/* Tombol Hapus */}
<Button <Button

View File

@ -46,3 +46,27 @@ export async function approveAgent(id: string | number) {
}; };
return await httpPutInterceptor(`/sales-agents/${id}/approve`, {}, headers); return await httpPutInterceptor(`/sales-agents/${id}/approve`, {}, headers);
} }
export async function rejectAgent(id: string | number, message?: string) {
const headers = {
"content-type": "application/json",
};
return await httpPutInterceptor(
`/sales-agents/${id}/reject`,
{ message },
headers,
);
}
export async function getApprovalHistory(
moduleType: string,
moduleId: string | number,
) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/approval-histories/${moduleType}/${moduleId}`,
headers,
);
}

View File

@ -56,3 +56,27 @@ export async function approveBanner(id: string | number) {
}; };
return await httpPutInterceptor(`/banners/${id}/approve`, {}, headers); return await httpPutInterceptor(`/banners/${id}/approve`, {}, headers);
} }
export async function rejectBanner(id: string | number, message?: string) {
const headers = {
"content-type": "application/json",
};
return await httpPutInterceptor(
`/banners/${id}/reject`,
{ message },
headers,
);
}
export async function getApprovalHistory(
moduleType: string,
moduleId: string | number,
) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/approval-histories/${moduleType}/${moduleId}`,
headers,
);
}

View File

@ -77,3 +77,27 @@ export async function approveGalery(id: string | number) {
}; };
return await httpPutInterceptor(`/galleries/${id}/approve`, {}, headers); return await httpPutInterceptor(`/galleries/${id}/approve`, {}, headers);
} }
export async function rejectGalery(id: string | number, message?: string) {
const headers = {
"content-type": "application/json",
};
return await httpPutInterceptor(
`/galleries/${id}/reject`,
{ message },
headers,
);
}
export async function getApprovalHistory(
moduleType: string,
moduleId: string | number,
) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/approval-histories/${moduleType}/${moduleId}`,
headers,
);
}

View File

@ -49,3 +49,27 @@ export async function approveProduct(id: string | number) {
}; };
return await httpPutInterceptor(`/products/${id}/approve`, {}, headers); return await httpPutInterceptor(`/products/${id}/approve`, {}, headers);
} }
export async function rejectProduct(id: string | number, message?: string) {
const headers = {
"content-type": "application/json",
};
return await httpPutInterceptor(
`/products/${id}/reject`,
{ message },
headers,
);
}
export async function getApprovalHistory(
moduleType: string,
moduleId: string | number,
) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/approval-histories/${moduleType}/${moduleId}`,
headers,
);
}

View File

@ -44,3 +44,27 @@ export async function approvePromotion(id: string | number) {
}; };
return await httpPutInterceptor(`/promotions/${id}/approve`, {}, headers); return await httpPutInterceptor(`/promotions/${id}/approve`, {}, headers);
} }
export async function rejectPromotion(id: string | number, message?: string) {
const headers = {
"content-type": "application/json",
};
return await httpPutInterceptor(
`/promotions/${id}/reject`,
{ message },
headers,
);
}
export async function getApprovalHistory(
moduleType: string,
moduleId: string | number,
) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/approval-histories/${moduleType}/${moduleId}`,
headers,
);
}