This commit is contained in:
Anang Yusman 2026-01-19 01:01:09 +08:00
parent 8f557bfc7e
commit 63e0890e08
36 changed files with 1663 additions and 143 deletions

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import ArticleTable from "@/components/table/article-table"; import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
@ -8,10 +8,25 @@ import { BannerDialog } from "@/components/form/banner-dialog";
import Link from "next/link"; import Link from "next/link";
import ProductTable from "@/components/table/product-table"; import ProductTable from "@/components/table/product-table";
import AgentTable from "@/components/table/agent-table"; import AgentTable from "@/components/table/agent-table";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
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 router = useRouter();
const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
console.log("Banner Data:", data); console.log("Banner Data:", data);
}; };
@ -26,12 +41,14 @@ 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 !== "3" && (
<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" />
Tambah Agent Tambah Agent
</Button> </Button>
</Link> </Link>
)}
<AgentTable /> <AgentTable />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import ArticleTable from "@/components/table/article-table"; import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
@ -10,12 +10,22 @@ import router from "next/router";
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 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 [userLevelId, setUserLevelId] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const [refreshKey, setRefreshKey] = useState(0);
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
const handleSubmitBanner = async (formData: FormData) => { const handleSubmitBanner = async (formData: FormData) => {
try { try {
const response = await createBanner(formData); const response = await createBanner(formData);
@ -48,6 +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 !== "3" && (
<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)}
@ -55,6 +66,7 @@ export default function BasicPage() {
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
Tambah Banner Tambah Banner
</Button> </Button>
)}
<ArticleTable /> <ArticleTable />
</div> </div>

View File

@ -0,0 +1,38 @@
"use client";
import { useState } from "react";
import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { BannerDialog } from "@/components/form/banner-dialog";
import Link from "next/link";
import ProductTable from "@/components/table/product-table";
import ServicesTable from "@/components/table/services-table";
import CostumerServiceTable from "@/components/table/costumer-service-table";
export default function CostumerServicePage() {
const [openDialog, setOpenDialog] = useState(false);
const handleSubmitBanner = (data: any) => {
console.log("Banner Data:", data);
// TODO: kirim data ke API di sini
};
return (
<div>
<div className="overflow-x-hidden overflow-y-scroll w-full">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="pl-3">
<h1 className="text-[#1F6779] text-2xl font-semibold">
Layanan Konsumen
</h1>
</div>
<div className="dark:bg-[#18181b] rounded-xl p-3">
<CostumerServiceTable />
</div>
</div>
</div>
</div>
);
}

View File

@ -1,14 +1,28 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import Galery from "@/components/table/galery"; import Galery from "@/components/table/galery";
import { GaleriDialog } from "@/components/dialog/galery-dialog"; import { GaleriDialog } from "@/components/dialog/galery-dialog";
import { useRouter } from "next/navigation";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
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 router = useRouter();
const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
const handleSubmitGaleri = () => { const handleSubmitGaleri = () => {
console.log("Submit galeri..."); console.log("Submit galeri...");
@ -25,6 +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 !== "3" && (
<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)}
@ -32,7 +47,7 @@ export default function GaleryPage() {
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
Tambah Galeri Baru Tambah Galeri Baru
</Button> </Button>
)}
<Galery /> <Galery />
</div> </div>
</div> </div>

View File

@ -1,15 +1,29 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import ArticleTable from "@/components/table/article-table"; import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { BannerDialog } from "@/components/form/banner-dialog"; import { BannerDialog } from "@/components/form/banner-dialog";
import Link from "next/link"; import Link from "next/link";
import ProductTable from "@/components/table/product-table"; import ProductTable from "@/components/table/product-table";
import { useRouter } from "next/navigation";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
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 router = useRouter();
const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
const handleSubmitBanner = (data: any) => { const handleSubmitBanner = (data: any) => {
console.log("Banner Data:", data); console.log("Banner Data:", data);
@ -26,12 +40,14 @@ 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 !== "3" && (
<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" />
Tambah Product Baru Tambah Product Baru
</Button> </Button>
</Link> </Link>
)}
<ProductTable /> <ProductTable />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import ArticleTable from "@/components/table/article-table"; import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
@ -9,10 +9,25 @@ import Link from "next/link";
import ProductTable from "@/components/table/product-table"; import ProductTable from "@/components/table/product-table";
import AgentTable from "@/components/table/agent-table"; import AgentTable from "@/components/table/agent-table";
import PromotionTable from "@/components/table/promotion-table"; import PromotionTable from "@/components/table/promotion-table";
import { useRouter } from "next/navigation";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
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 router = useRouter();
const MySwal = withReactContent(Swal);
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
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 // TODO: kirim data ke API di sini
@ -28,12 +43,14 @@ 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 !== "3" && (
<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" />
Tambah Promo Baru Tambah Promo Baru
</Button> </Button>
</Link> </Link>
)}
<PromotionTable /> <PromotionTable />
</div> </div>
</div> </div>

View File

@ -0,0 +1,35 @@
"use client";
import { useState } from "react";
import ArticleTable from "@/components/table/article-table";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { BannerDialog } from "@/components/form/banner-dialog";
import Link from "next/link";
import ProductTable from "@/components/table/product-table";
import ServicesTable from "@/components/table/services-table";
export default function ServicesPage() {
const [openDialog, setOpenDialog] = useState(false);
const handleSubmitBanner = (data: any) => {
console.log("Banner Data:", data);
// TODO: kirim data ke API di sini
};
return (
<div>
<div className="overflow-x-hidden overflow-y-scroll w-full">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="pl-3">
<h1 className="text-[#1F6779] text-2xl font-semibold">Services</h1>
</div>
<div className="dark:bg-[#18181b] rounded-xl p-3">
<ServicesTable />
</div>
</div>
</div>
</div>
);
}

View File

@ -1,19 +1,19 @@
import Exterior from "@/components/landing-page/exterior"; import ExteriorShs from "@/components/landing-page/exterior-shs";
import FeaturesAndSpecifications from "@/components/landing-page/features-and-specifications"; import FeaturesAndSpecificationsShs from "@/components/landing-page/features-and-specifications-shs";
import Footer from "@/components/landing-page/footer"; import Footer from "@/components/landing-page/footer";
import HeaderProductJ7Awd from "@/components/landing-page/header-product-j7-awd"; import HeaderProductJ7Shs from "@/components/landing-page/header-product-j7-shs";
import Interior from "@/components/landing-page/interior"; import InteriorShs from "@/components/landing-page/interior-shs";
import Navbar from "@/components/landing-page/navbar"; import Navbar from "@/components/landing-page/navbar";
export default function ProductJ7Page() { export default function ProductJ7ShsPage() {
return ( return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]"> <div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-white w-full mx-auto"> <div className="relative z-10 bg-white w-full mx-auto">
<Navbar /> <Navbar />
<HeaderProductJ7Awd /> <HeaderProductJ7Shs />
<Exterior /> <ExteriorShs />
<Interior /> <InteriorShs />
<FeaturesAndSpecifications /> <FeaturesAndSpecificationsShs />
<Footer /> <Footer />
</div> </div>
</div> </div>

View File

@ -0,0 +1,21 @@
import Exterior from "@/components/landing-page/exterior";
import FeaturesAndSpecifications from "@/components/landing-page/features-and-specifications";
import Footer from "@/components/landing-page/footer";
import HeaderProductJ7Awd from "@/components/landing-page/header-product-j7-awd";
import Interior from "@/components/landing-page/interior";
import Navbar from "@/components/landing-page/navbar";
export default function ProductJ7Page() {
return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-white w-full mx-auto">
<Navbar />
<HeaderProductJ7Awd />
<Exterior />
<Interior />
<FeaturesAndSpecifications />
<Footer />
</div>
</div>
);
}

View File

@ -1,21 +0,0 @@
import ExteriorShs from "@/components/landing-page/exterior-shs";
import FeaturesAndSpecificationsShs from "@/components/landing-page/features-and-specifications-shs";
import Footer from "@/components/landing-page/footer";
import HeaderProductJ7Shs from "@/components/landing-page/header-product-j7-shs";
import InteriorShs from "@/components/landing-page/interior-shs";
import Navbar from "@/components/landing-page/navbar";
export default function ProductJ7ShsPage() {
return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-white w-full mx-auto">
<Navbar />
<HeaderProductJ7Shs />
<ExteriorShs />
<InteriorShs />
<FeaturesAndSpecificationsShs />
<Footer />
</div>
</div>
);
}

View File

@ -76,7 +76,9 @@ export default function ExteriorJ8Awd() {
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="text-2xl mt-5 mb-8" className="text-2xl mt-5 mb-8"
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 8 AWD</span>{" "} <span className="text-[#1F6779] font-semibold">
Jaecoo 8 SHS-P ARDIS
</span>{" "}
Teknologi dan Exterior Teknologi dan Exterior
</motion.h2> </motion.h2>

View File

@ -9,7 +9,7 @@ const featuresshs = [
{ {
title: "Rear view mirrors", title: "Rear view mirrors",
description: description:
"The mirrors on the pillars are a discreet but aesthetic design detail of the Jaecoo J7 SHS. Their contrasting inserts harmoniously resonate with other accent touches of the exterior.", "The mirrors on the pillars are a discreet but aesthetic design detail of the Jaecoo J5 EV. Their contrasting inserts harmoniously resonate with other accent touches of the exterior.",
image: "/ex-shs3.png", image: "/ex-shs3.png",
}, },
{ {
@ -53,7 +53,7 @@ export default function ExteriorShs() {
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="text-2xl mt-5 mb-8" className="text-2xl mt-5 mb-8"
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "}
Teknologi dan Exterior Teknologi dan Exterior
</motion.h2> </motion.h2>

View File

@ -53,7 +53,7 @@ export default function Exterior() {
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="text-2xl mt-5 mb-8" className="text-2xl mt-5 mb-8"
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS-P</span>{" "}
Teknologi dan Exterior Teknologi dan Exterior
</motion.h2> </motion.h2>

View File

@ -6,7 +6,10 @@ export default function FeaturesAndSpecificationsJ8() {
return ( return (
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white"> <section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 8 AWD</span> Fitur <span className="text-[#1F6779] font-semibold">
Jaecoo 8 SHS-P ARDIS
</span>{" "}
Fitur
</h2> </h2>
<div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]"> <div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]">
@ -39,7 +42,9 @@ export default function FeaturesAndSpecificationsJ8() {
/> />
</div> </div>
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 8 AWD</span>{" "} <span className="text-[#1F6779] font-semibold">
Jaecoo 8 SHS-P ARDIS
</span>{" "}
Spesifikasi Spesifikasi
</h2> </h2>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10">

View File

@ -6,7 +6,7 @@ export default function FeaturesAndSpecificationsShs() {
return ( return (
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white"> <section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS</span> Fitur <span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span> Fitur
</h2> </h2>
<div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]"> <div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]">
@ -38,7 +38,7 @@ export default function FeaturesAndSpecificationsShs() {
/> />
</div> </div>
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "}
Spesifikasi Spesifikasi
</h2> </h2>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10">

View File

@ -6,7 +6,7 @@ export default function FeaturesAndSpecifications() {
return ( return (
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white"> <section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS-P</span>{" "}
Interior Interior
</h2> </h2>
@ -39,7 +39,7 @@ export default function FeaturesAndSpecifications() {
/> />
</div> </div>
<h2 className="text-2xl mt-5 mb-8"> <h2 className="text-2xl mt-5 mb-8">
<span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS-P</span>{" "}
Spesifikasi Spesifikasi
</h2> </h2>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-32 gap-y-6 text-sm sm:text-base text-start my-10">

View File

@ -8,7 +8,7 @@ import { useState } from "react";
export default function HeaderAfterSalesServices() { export default function HeaderAfterSalesServices() {
const cars = [ const cars = [
{ {
title: "JAECOO J7 AWD", title: "JAECOO J7 SHS-PV-P",
image: "/j7-awd-nobg.png", image: "/j7-awd-nobg.png",
price: "Rp 549.000.000", price: "Rp 549.000.000",
oldPrice: "Rp 544.000.000", oldPrice: "Rp 544.000.000",
@ -18,7 +18,7 @@ export default function HeaderAfterSalesServices() {
display: `14.8"`, display: `14.8"`,
}, },
{ {
title: "JAECOO J7 SHS", title: "JAECOO J5 EV",
image: "/j7-shs-nobg.png", image: "/j7-shs-nobg.png",
price: "Rp 599.000.000", price: "Rp 599.000.000",
oldPrice: "Rp 594.000.000", oldPrice: "Rp 594.000.000",
@ -28,7 +28,7 @@ export default function HeaderAfterSalesServices() {
display: `14.8"`, display: `14.8"`,
}, },
{ {
title: "JAECOO J8 AWD", title: "JAECOO J8 SHS-P ARDIS",
image: "/j8-awd-nobg.png", image: "/j8-awd-nobg.png",
price: "Rp 812.000.000", price: "Rp 812.000.000",
oldPrice: "Rp 807.000.000", oldPrice: "Rp 807.000.000",

View File

@ -19,31 +19,36 @@ export default function HeaderPriceInformation() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const cars = [ const cars = [
{ {
title: "JAECOO J7 AWD", title: "JAECOO J7 SHS-P",
image: "/j7-awd-nobg.png", image: "/j7-awd-nobg.png",
price: "Rp 549.000.000", price: "Rp 509.900.000",
oldPrice: "Rp 544.000.000", oldPrice: "Rp 509.900.000",
capacity: "18.3kWh", capacity: "18.3kWh",
power: "-",
torque: "310 N.m",
wheels: `19"`, wheels: `19"`,
seats: "Leather", seats: "Leather",
display: `14.8"`, display: `14.8"`,
}, },
{ {
title: "JAECOO J7 SHS", title: "JAECOO J5 EV",
image: "/j7-shs-nobg.png", image: "/j7-shs-nobg.png",
price: "Rp 599.000.000", price: "Rp 299.900.000",
oldPrice: "Rp 594.000.000", oldPrice: "Rp 299.900.000",
capacity: "18.3kWh", capacity: "60.9kWh",
power: "155kW",
torque: "288Nm",
wheels: `19"`, wheels: `19"`,
seats: "Leather", seats: "Leather",
display: `14.8"`, display: `14.8"`,
}, },
{ {
title: "JAECOO J8 AWD", title: "JAECOO J8 SHS-P ARDIS",
image: "/j8-awd-nobg.png", image: "/j8-awd-nobg.png",
price: "Rp 812.000.000", price: "Rp 812.000.000",
oldPrice: "Rp 807.000.000", oldPrice: "Rp 828.000.000",
capacity: "18.3kWh", capacity: "34,46kWh",
torque: "650Nm",
wheels: `19"`, wheels: `19"`,
seats: "Leather", seats: "Leather",
display: `14.8"`, display: `14.8"`,
@ -119,10 +124,10 @@ export default function HeaderPriceInformation() {
</svg> </svg>
</div> </div>
</div> </div>
<p className="text-[15px] text-black text-start mb-4"> {/* <p className="text-[15px] text-black text-start mb-4">
*Save Rp 5.000.000 on the previous driveway price of{" "} *Save Rp 5.000.000 on the previous driveway price of{" "}
{car.oldPrice}. Offer ends 31st August 2025. {car.oldPrice}. Offer ends 31st August 2025.
</p> </p> */}
<div className="grid grid-cols-2 gap-2 w-full text-lg text-center mb-4"> <div className="grid grid-cols-2 gap-2 w-full text-lg text-center mb-4">
<div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2"> <div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2">
<svg <svg
@ -158,8 +163,8 @@ export default function HeaderPriceInformation() {
/> />
</svg> </svg>
<div className="flex flex-col items-center justify-center text-center"> <div className="flex flex-col items-center justify-center text-center">
<span className="font-bold">{car.wheels}</span>{" "} <span className="font-bold">{car.power}</span>{" "}
<span className="text-sm">Alloy Wheels</span> <span className="text-sm">Max Power</span>
</div> </div>
</div> </div>
<div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2"> <div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2">
@ -167,16 +172,28 @@ export default function HeaderPriceInformation() {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
viewBox="0 0 512 512" viewBox="0 0 48 48"
> >
<path <path
fill="currentColor" // fill="none"
d="m71.47 18.38l-.01.01c-6.58-.1-14.25.79-21.52 2.41c-8.31 1.84-16.18 4.69-21.3 7.56c-2.57 1.44-4.42 2.9-5.24 3.8l25.86 90.54c7.22-9.1 15.41-16.6 23.75-22.2c9.69-6.44 19.19-10.67 27.89-12.47c0-13.14-.3-25.92-1.8-36.76c-1.9-13.05-5.6-23.03-11.5-28.91c-1.3-1.35-6.28-3.44-13.39-3.88c-.89 0-1.81-.1-2.74-.1m29.03 92.12c-6.7.4-14.2 3.5-21.1 8.7c-13.68 10.3-24.04 28.7-24.34 40.2l45.74 240.3c7.6-9.5 19.2-15.7 32.2-15.7c11.5 0 22 4.9 29.5 12.7c5.1-1.1 10.5-2.2 16.4-3.3c1.5-.3 3.1-.5 4.7-.8c-13.5-92.5-35.3-199.6-65.2-275.3c-5.2-4.8-10.3-6.7-15.6-6.8zm283 39.5l-53.6 167.4l17.2 5.4l24-75.1l117.1 37.5l5.4-17.2l-117-37.4l24.1-75.2zm-38.7 245.3c-21.5.1-46.3 1.4-71 3.7c-33 2.9-66 7.4-91.6 12.1c-3.5.6-6.8 1.3-10 1.9q1.8 5.7 1.8 12c0 22.5-18.5 41-41 41c-5.6 0-11-1.2-15.9-3.2c-3.1 8.9-5.4 17.6-6.7 24.2H398c5 0 7.7-1.8 10.7-6.4c3.1-4.7 5.4-12.4 6.3-21.5c1.9-18.1-2.1-41.2-9.1-55.1c.3.5-2.8-2.5-10.2-4.4s-18.1-3.3-30.7-3.9c-6.3-.3-13.1-.4-20.2-.4M133 402c-12.8 0-23 10.2-23 23s10.2 23 23 23s23-10.2 23-23s-10.2-23-23-23" // stroke="currentColor"
// stroke-linecap="round"
// stroke-linejoin="round"
d="M28.413 30.857v-7.529h1.694a3.294 3.294 0 0 1 3.293 3.294v.941a3.294 3.294 0 0 1-3.293 3.294ZM14.6 28.363a2.494 2.494 0 0 0 4.987 0v-2.54a2.494 2.494 0 0 0-4.987 0Zm10.096-1.27a1.882 1.882 0 0 1 0 3.764h-3.105v-7.529h3.105a1.882 1.882 0 0 1 0 3.764m0 .001h-3.105m-3.448-12.39a1.7 1.7 0 0 1 0-3.39h12a1.7 1.7 0 1 1 0 3.39Zm-10.25 16.87a1.7 1.7 0 0 1-3.39 0h0v-10.17a1.7 1.7 0 1 1 3.39 0Zm0-5.09h2.109m14.14-9.021v-2.759"
stroke-width="1"
/>
<path
// fill="none"
// stroke="currentColor"
// stroke-linecap="round"
// stroke-linejoin="round"
d="M36.773 25.063h2.3v-3.26h2.11l2.32 7.2l-2.33 7.2h-2.1v-3.14h-2.3l.02 2.05a1.58 1.58 0 0 1-1.58 1.58h-13.56a1.17 1.17 0 0 1-.66-.2l-4.99-3.47h-4.21a1.79 1.79 0 0 1-1.79-1.79v-.041h0v-9.4a1.8 1.8 0 0 1 1.79-1.77h1.4l2.66-2.26a1.13 1.13 0 0 1 .73-.27h15.26a1.59 1.59 0 0 1 1.58 1.53v2.15h1.79a1.59 1.59 0 0 1 1.58 1.59h0Z"
stroke-width="1"
/> />
</svg> </svg>
<div className="flex flex-col items-center justify-center text-center ml-5"> <div className="flex flex-col items-center justify-center text-center ml-5">
<span className="font-bold">{car.seats}</span>{" "} <span className="font-bold">{car.torque}</span>{" "}
<span className="text-sm"> Seats</span> <span className="text-sm"> Torque</span>
</div> </div>
</div> </div>
<div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2"> <div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2">

View File

@ -65,7 +65,7 @@ export default function Header() {
transition={{ duration: 0.8, ease: "easeOut" }} transition={{ duration: 0.8, ease: "easeOut" }}
className="text-2xl sm:text-3xl md:text-5xl font-bold text-black mb-4" className="text-2xl sm:text-3xl md:text-5xl font-bold text-black mb-4"
> >
JAECOO J7 AWD JAECOO J7 SHS-P
</motion.h1> </motion.h1>
<motion.p <motion.p

View File

@ -35,7 +35,9 @@ export default function InteriorJ8Awd() {
animate={inView ? { opacity: 1, y: 0 } : {}} animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 8 AWD</span>{" "} <span className="text-[#1F6779] font-semibold">
Jaecoo 8 SHS-P ARDIS
</span>{" "}
Interior Interior
</motion.h2> </motion.h2>

View File

@ -67,7 +67,7 @@ export default function InteriorShs() {
animate={inView ? { opacity: 1, y: 0 } : {}} animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS</span>{" "} <span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span>{" "}
Interior Interior
</motion.h2> </motion.h2>

View File

@ -57,7 +57,8 @@ export default function Interior() {
animate={inView ? { opacity: 1, y: 0 } : {}} animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
> >
<span className="text-[#1F6779] font-semibold">Jaecoo 5 EV</span> Fitur <span className="text-[#1F6779] font-semibold">Jaecoo 7 SHS-P</span>{" "}
Fitur
</motion.h2> </motion.h2>
<motion.div <motion.div

View File

@ -18,19 +18,19 @@ import { useState } from "react";
const items = [ const items = [
{ {
image: "/new-car2.png", image: "/new-car2.png",
title: "JAECOO J7 AWD", title: "JAECOO J7 SHS-P",
description: "DELICATE OFF-ROAD SUV", description: "DELICATE OFF-ROAD SUV",
link: "/product/j7-awd", link: "/product/j7-awd",
}, },
{ {
image: "/new-car1.png", image: "/new-car1.png",
title: "JAECOO J7 SHS", title: "JAECOO J5 EV",
description: "SUPER HYBRID SYSTEM = SUPER HEV + EV", description: "SUPER HYBRID SYSTEM = SUPER HEV + EV",
link: "/product/j7-shs", link: "/product/j7-shs",
}, },
{ {
image: "/new-car3.png", image: "/new-car3.png",
title: "JAECOO J8 AWD", title: "JAECOO J8 SHS-P ARDIS",
description: "FIRST CLASS OFF-ROAD", description: "FIRST CLASS OFF-ROAD",
link: "/product/j8-awd", link: "/product/j8-awd",
}, },

View File

@ -47,19 +47,19 @@ export default function Navbar() {
const produkList = [ const produkList = [
{ {
name: "JAECOO J5 EV", name: "JAECOO J7 SHS-P",
img: "/j7awd.png", img: "/j7awd.png",
link: "/product/j7-shs-p",
},
{
name: "JAECOO J5 EV",
img: "/j7shs.png",
link: "/product/j5-ev", link: "/product/j5-ev",
}, },
{ {
name: "JAECOO J7 SHS", name: "JAECOO J8 SHS-P ARDIS",
img: "/j7shs.png",
link: "/product/j7-shs",
},
{
name: "JAECOO J8 AWD",
img: "/j8awd.png", img: "/j8awd.png",
link: "/product/j8-awd", link: "/product/j8-shs-ardis",
}, },
]; ];
const priceList = [ const priceList = [

View File

@ -84,7 +84,7 @@ const sidebarSections = [
height="18" height="18"
/> />
), ),
link: "/admin/costumer-support", link: "/admin/costumer-service",
}, },
{ {
title: "Manajemen User", title: "Manajemen User",

View File

@ -102,7 +102,7 @@ export default function DashboardContainer() {
no: 3, no: 3,
tanggal: "09/11/2024", tanggal: "09/11/2024",
jenis: "Produk", jenis: "Produk",
judul: "JAECOO J7 AWD Update", judul: "JAECOO J7 SHS-P Update",
status: "Disetujui", status: "Disetujui",
}, },
{ {
@ -131,12 +131,12 @@ export default function DashboardContainer() {
const notifications = [ const notifications = [
{ {
icon: "✅", icon: "✅",
text: 'Upload "JAECOO J7 AWD Update" telah disetujui oleh Admin Manager', text: 'Upload "JAECOO J7 SHS-P Update" telah disetujui oleh Admin Manager',
time: "2 jam yang lalu", time: "2 jam yang lalu",
}, },
{ {
icon: "❌", icon: "❌",
text: 'Upload "Brosur JAECOO J8" ditolak. Alasan: Resolusi gambar terlalu rendah.', text: 'Upload "Brosur JAECOO J8 SHS-P ARDIS" ditolak. Alasan: Resolusi gambar terlalu rendah.',
time: "2 jam yang lalu", time: "2 jam yang lalu",
}, },
{ {

View File

@ -523,7 +523,7 @@ export default function AgentTable() {
</button> </button>
<h2 className="text-lg font-semibold">JAEC00 J7 AWD</h2> <h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p> <p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */} {/* Status badge */}

View File

@ -47,7 +47,7 @@ import {
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 { deleteBanner, getBannerData, updateBanner } from "@/service/banner"; import { deleteBanner, getBannerData, updateBanner } from "@/service/banner";
import { CheckCheck } from "lucide-react"; import { CheckCheck, Eye } from "lucide-react";
const columns = [ const columns = [
{ name: "No", uid: "no" }, { name: "No", uid: "no" },
@ -170,8 +170,20 @@ export default function ArticleTable() {
const [openEditDialog, setOpenEditDialog] = useState(false); const [openEditDialog, setOpenEditDialog] = useState(false);
const [selectedBanner, setSelectedBanner] = useState<any>(null); const [selectedBanner, setSelectedBanner] = useState<any>(null);
const [openPreview, setOpenPreview] = useState(false); const [openPreview, setOpenPreview] = useState(false);
const [openViewDialog, setOpenViewDialog] = useState(false);
const [viewBanner, setViewBanner] = useState<any>(null);
const [openApproverHistory, setOpenApproverHistory] = useState(false);
const handleView = (item: any) => {
setViewBanner(item);
setOpenViewDialog(true);
};
const [previewImage, setPreviewImage] = useState<string | null>(null); const [previewImage, setPreviewImage] = useState<string | null>(null);
const handleOpenApproverHistory = () => {
setOpenApproverHistory(true);
};
const handleEdit = (item: any) => { const handleEdit = (item: any) => {
setSelectedBanner({ setSelectedBanner({
id: item.id, id: item.id,
@ -211,19 +223,7 @@ export default function ArticleTable() {
switch (columnKey) { switch (columnKey) {
case "isPublish": case "isPublish":
return ( return <p>{article.isPublish ? "Publish" : "Draft"}</p>;
// <Chip
// className="capitalize "
// color={statusColorMap[article.status]}
// size="lg"
// variant="flat"
// >
// <div className="flex flex-row items-center gap-2 justify-center">
// {article.status}
// </div>
// </Chip>
<p>{article.isPublish ? "Publish" : "Draft"}</p>
);
case "isBanner": case "isBanner":
return <p>{article.isBanner ? "Ya" : "Tidak"}</p>; return <p>{article.isBanner ? "Ya" : "Tidak"}</p>;
case "createdAt": case "createdAt":
@ -424,10 +424,10 @@ export default function ArticleTable() {
variant="ghost" variant="ghost"
size="sm" size="sm"
className="text-[#0F6C75] hover:bg-transparent hover:underline p-0" className="text-[#0F6C75] hover:bg-transparent hover:underline p-0"
// onClick={() => handleEdit(item)} onClick={() => handleView(item)}
> >
<CheckCheck className="w-4 h-4 mr-1" /> <Eye className="w-4 h-4 mr-1" />
Approve Lihat
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
@ -521,7 +521,7 @@ export default function ArticleTable() {
</button> </button>
<h2 className="text-lg font-semibold">JAEC00 J7 AWD</h2> <h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p> <p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */} {/* Status badge */}
@ -556,6 +556,240 @@ export default function ArticleTable() {
</div> </div>
</div> </div>
)} )}
{openViewDialog && viewBanner && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onClick={() => setOpenViewDialog(false)}
>
<div
className="bg-white rounded-2xl shadow-2xl max-w-xl w-full overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* HEADER */}
<div className="bg-gradient-to-br from-[#1F6779] to-[#0F6C75] text-white px-6 py-5 relative">
<button
onClick={() => setOpenViewDialog(false)}
className="absolute top-4 right-4 text-white/80 hover:text-white text-xl"
>
</button>
<h2 className="text-lg font-semibold">Detail Banner</h2>
{/* Badge */}
<div className="flex items-center gap-2 mt-3">
<span
className={`text-xs font-medium px-3 py-1 rounded-full
${
viewBanner.status === "Menunggu"
? "bg-yellow-100 text-yellow-800"
: viewBanner.status === "Disetujui"
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{viewBanner.status}
</span>
<span className="bg-white text-[#0F6C75] text-xs font-medium px-3 py-1 rounded-full">
Banner
</span>
<span className="bg-white/20 text-white text-xs px-2 py-[2px] rounded-full">
{viewBanner.position}
</span>
</div>
</div>
{/* BODY */}
<div className="p-6 space-y-6">
{/* JUDUL */}
<div>
<label className="block text-sm font-medium text-gray-500 mb-2">
Judul Banner <span className="text-red-500">*</span>
</label>
<div className="border rounded-lg p-3 text-gray-800 bg-gray-50 whitespace-pre-line">
{viewBanner.title}
</div>
</div>
{/* IMAGE */}
<div>
<label className="block text-sm font-medium text-gray-500 mb-2">
Upload File <span className="text-red-500">*</span>
</label>
<div className="w-[140px] h-[140px] rounded-lg overflow-hidden border bg-gray-100">
{viewBanner.thumbnail_url ? (
<img
src={viewBanner.thumbnail_url}
alt={viewBanner.title}
className="w-full h-full object-cover"
/>
) : (
<div className="flex items-center justify-center h-full text-gray-400 text-xs">
No Image
</div>
)}
</div>
</div>
{/* TIMELINE */}
<div>
<h4 className="text-sm font-semibold text-gray-700 mb-3">
Status Timeline
</h4>
<div className="space-y-4">
<div className="flex gap-3">
<div className="w-6 h-6 rounded-full bg-green-100 flex items-center justify-center">
<CheckCheck className="w-4 h-4 text-green-600" />
</div>
<div>
<p className="font-medium text-gray-800">
Diupload oleh {viewBanner.createdByName}
</p>
<p className="text-sm text-gray-500">
{convertDateFormat(viewBanner.created_at)} WIB
</p>
</div>
</div>
<div className="flex gap-3">
<div className="w-6 h-6 rounded-full bg-yellow-100 flex items-center justify-center">
</div>
<div>
<p className="font-medium text-gray-800">
Menunggu disetujui oleh Approver
</p>
<p className="text-sm text-gray-500">
{convertDateFormat(viewBanner.updated_at)} WIB
</p>
</div>
</div>
<button
onClick={handleOpenApproverHistory}
className="text-sm text-blue-600 hover:underline mt-2"
>
View Approver History
</button>
</div>
</div>
</div>
{/* FOOTER */}
<div className="flex justify-between items-center gap-3 px-6 py-4 border-t bg-[#F2F7FA]">
<Button variant="secondary">Beri Tanggapan</Button>
<div className="flex gap-3">
<Button variant="destructive">Reject</Button>
<Button className="bg-green-600 hover:bg-green-700 text-white">
Approved
</Button>
</div>
</div>
</div>
</div>
)}
{openApproverHistory && (
<div
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4"
onClick={() => setOpenApproverHistory(false)}
>
<div
className="bg-white rounded-2xl shadow-2xl max-w-3xl w-full overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* HEADER */}
<div className="bg-gradient-to-br from-[#1F6779] to-[#0F6C75] text-white px-6 py-5 relative">
<button
onClick={() => setOpenApproverHistory(false)}
className="absolute top-4 right-4 text-white/80 hover:text-white text-xl"
>
</button>
<h2 className="text-lg font-semibold">Approver History</h2>
<div className="flex items-center gap-2 mt-3">
<span className="bg-yellow-100 text-yellow-800 text-xs font-medium px-3 py-1 rounded-full">
Menunggu
</span>
<span className="bg-white text-[#0F6C75] text-xs font-medium px-3 py-1 rounded-full">
Banner
</span>
<span className="bg-white/20 text-white text-xs px-2 py-[2px] rounded-full">
1
</span>
</div>
</div>
{/* BODY */}
<div className="p-6 grid grid-cols-[1fr_auto_1fr] gap-6 items-start">
{/* LEFT TIMELINE */}
<div className="relative space-y-6">
{/* Upload */}
<div className="flex flex-col items-center">
<span className="bg-[#C7DDE4] text-[#0F6C75] text-xs px-4 py-1 rounded-full">
Upload
</span>
<div className="w-px h-6 bg-gray-300" />
</div>
{/* Diterima */}
<div className="relative bg-[#1F6779] text-white rounded-xl p-4">
<h4 className="font-semibold text-sm mb-2">Diterima</h4>
<span className="inline-block bg-[#E3EFF4] text-[#0F6C75] text-xs px-3 py-1 rounded-full">
Direview oleh: approver-jaecoo1
</span>
</div>
<div className="w-px h-6 bg-gray-300 mx-auto" />
{/* Pending */}
<div className="relative bg-[#B36A00] text-white rounded-xl p-4">
<h4 className="font-semibold text-sm mb-2">Pending</h4>
<span className="inline-block bg-[#FFF6CC] text-[#7A4A00] text-xs px-3 py-1 rounded-full">
Direview oleh: approver-jaecoo1
</span>
</div>
</div>
{/* ARROW */}
<div className="flex flex-col gap-20 text-gray-500 font-bold">
<span>&gt;</span>
<span>&gt;</span>
</div>
{/* RIGHT NOTES */}
<div className="space-y-14">
<div>
<div className="bg-[#C7DDE4] text-sm px-4 py-2 rounded-lg">
Catatan:
</div>
</div>
<div>
<div className="bg-[#FFF9C4] text-sm px-4 py-2 rounded-lg">
Catatan:
</div>
</div>
</div>
</div>
{/* FOOTER */}
<div className="border-t bg-[#F2F7FA] text-center py-3">
<button
onClick={() => setOpenApproverHistory(false)}
className="text-[#0F6C75] font-medium hover:underline"
>
Tutup
</button>
</div>
</div>
</div>
)}
</> </>
); );
} }

View File

@ -0,0 +1,505 @@
"use client";
import {
BannerIcon,
CopyIcon,
CreateIconIon,
DeleteIcon,
DotsYIcon,
EyeIconMdi,
SearchIcon,
} from "@/components/icons";
import { close, error, loading, success, successToast } from "@/config/swal";
import { Article } from "@/types/globals";
import { convertDateFormat } from "@/utils/global";
import Link from "next/link";
import { Key, useCallback, useEffect, useState } from "react";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import Cookies from "js-cookie";
import {
deleteArticle,
getArticleByCategory,
getArticlePagination,
updateIsBannerArticle,
} from "@/service/article";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@/components/ui/table";
import CustomPagination from "../layout/custom-pagination";
import { EditBannerDialog } from "../form/banner-edit-dialog";
import { deleteProduct, getProductPagination } from "@/service/product";
import { CheckCheck, Plus } from "lucide-react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "../ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import { Label } from "../ui/label";
import { useRouter } from "next/navigation";
const columns = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Banner", uid: "isBanner" },
{ name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" },
{ name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" },
];
const columnsOtherRole = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" },
{ name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" },
];
// interface Category {
// id: number;
// title: string;
// }
export default function CostumerServiceTable() {
const MySwal = withReactContent(Swal);
const username = Cookies.get("username");
const userId = Cookies.get("uie");
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<any[]>([]);
const [showData, setShowData] = useState("10");
const [search, setSearch] = useState("");
const [categories, setCategories] = useState<any>([]);
const [selectedCategories, setSelectedCategories] = useState<any>("");
const [startDateValue, setStartDateValue] = useState({
startDate: null,
endDate: null,
});
const [userLevelId, setUserLevelId] = useState<string | null>(null);
const router = useRouter();
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
useEffect(() => {
initState();
getCategories();
}, []);
async function getCategories() {
const res = await getArticleByCategory();
const data = res?.data?.data;
setCategories(data);
}
const initState = useCallback(async () => {
loading();
const req = {
limit: showData,
page: page,
search: search,
};
const res = await getProductPagination(req);
await getTableNumber(parseInt(showData), res.data?.data);
setTotalPage(res?.data?.meta?.totalPage);
close();
}, [page]);
const getTableNumber = async (limit: number, data: Article[]) => {
if (data) {
const startIndex = limit * (page - 1);
let iterate = 0;
const newData = data.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
setArticle(newData);
} else {
setArticle([]);
}
};
async function doDelete(id: any) {
// loading();
const resDelete = await deleteProduct(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
success("Berhasil Hapus");
initState();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
const handleBanner = async (id: number, status: boolean) => {
const res = await updateIsBannerArticle(id, status);
if (res?.error) {
error(res?.message);
return false;
}
initState();
};
const [openEditDialog, setOpenEditDialog] = useState(false);
const [selectedBanner, setSelectedBanner] = useState<any>(null);
const [openPreview, setOpenPreview] = useState(false);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const handleUpdateBanner = (data: any) => {
console.log("Updated banner data:", data);
// TODO: panggil API update di sini
// lalu refresh tabel
};
const handlePreview = (imgUrl: string) => {
setPreviewImage(imgUrl);
setOpenPreview(true);
};
const copyUrlArticle = async (id: number, slug: string) => {
const url =
`${window.location.protocol}//${window.location.host}` +
"/news/detail/" +
`${id}-${slug}`;
try {
await navigator.clipboard.writeText(url);
successToast("Success", "Article Copy to Clipboard");
setTimeout(() => {}, 1500);
} catch (err) {
("Failed to copy!");
}
};
const renderCell = useCallback(
(article: any, columnKey: Key) => {
const cellValue = article[columnKey as keyof any];
switch (columnKey) {
case "isPublish":
return (
// <Chip
// className="capitalize "
// color={statusColorMap[article.status]}
// size="lg"
// variant="flat"
// >
// <div className="flex flex-row items-center gap-2 justify-center">
// {article.status}
// </div>
// </Chip>
<p>{article.isPublish ? "Publish" : "Draft"}</p>
);
case "isBanner":
return <p>{article.isBanner ? "Ya" : "Tidak"}</p>;
case "createdAt":
return <p>{convertDateFormat(article.createdAt)}</p>;
case "category":
return (
<p>
{article?.categories?.map((list: any) => list.title).join(", ") +
" "}
</p>
);
case "actions":
return (
<div className="relative flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<DotsYIcon className="h-5 w-5 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={() => copyUrlArticle(article.id, article.slug)}
>
<CopyIcon className="mr-2 h-4 w-4" />
Copy Url Article
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`/admin/article/detail/${article.id}`}
className="flex items-center"
>
<EyeIconMdi className="mr-2 h-4 w-4" />
Detail
</Link>
</DropdownMenuItem>
{(username === "admin-mabes" ||
Number(userId) === article.createdById) && (
<DropdownMenuItem asChild>
<Link
href={`/admin/article/edit/${article.id}`}
className="flex items-center"
>
<CreateIconIon className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
)}
{username === "admin-mabes" && (
<DropdownMenuItem
onClick={() =>
handleBanner(article.id, !article.isBanner)
}
>
<BannerIcon className="mr-2 h-4 w-4" />
{article.isBanner
? "Hapus dari Banner"
: "Jadikan Banner"}
</DropdownMenuItem>
)}
{(username === "admin-mabes" ||
Number(userId) === article.createdById) && (
<DropdownMenuItem onClick={() => handleDelete(article.id)}>
<DeleteIcon className="mr-2 h-4 w-4 text-red-500" />
Delete
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
default:
return cellValue;
}
},
[article, page]
);
let typingTimer: NodeJS.Timeout;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
setPage(1);
initState();
}
return (
<>
<div className="py-3">
<div className="w-full overflow-x-auto ">
{/* Header */}
<Tabs defaultValue="after-sales">
<TabsList className="py-3 px-3 bg-[#1F6779] rounded-sm">
<TabsTrigger value="after-sales" className="px-3 py-3 ">
After Sales
</TabsTrigger>
<TabsTrigger value="sales" className="px-3 py-3 ">
Sales
</TabsTrigger>
</TabsList>
{userLevelId !== "3" && (
<Link href={"/admin/product/create"}>
<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" />
Tambah After Sales
</Button>
</Link>
)}
<TabsContent
value="after-sales"
className="shadow-sm border border-gray-200 "
>
<div className="bg-[#0F6C75] text-white text-lg rounded-t-sm px-6 py-3">
Daftar After Sales
</div>
{/* FOOTER PAGINATION */}
<div className="flex items-center justify-between px-6 py-3 border-t text-sm text-gray-600">
<p>
Menampilkan {article.length} dari {article.length} data
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Previous
</Button>
<p>
Halaman {page} dari {totalPage}
</p>
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === totalPage}
onClick={() => setPage(page + 1)}
>
Next
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="sales">
<div className="bg-[#0F6C75] text-white text-lg rounded-t-sm px-6 py-3">
Daftar Sales
</div>
{/* FOOTER PAGINATION */}
<div className="flex items-center justify-between px-6 py-3 border-t text-sm text-gray-600">
<p>
Menampilkan {article.length} dari {article.length} data
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Previous
</Button>
<p>
Halaman {page} dari {totalPage}
</p>
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === totalPage}
onClick={() => setPage(page + 1)}
>
Next
</Button>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
<EditBannerDialog
open={openEditDialog}
onOpenChange={setOpenEditDialog}
bannerData={selectedBanner}
onSubmit={handleUpdateBanner}
/>
{/* Preview Dialog */}
{openPreview && (
<div
className="fixed inset-0 flex items-center justify-center bg-black/50 z-50 p-4"
onClick={() => setOpenPreview(false)}
>
<div
className="bg-white rounded-xl overflow-hidden shadow-2xl max-w-md w-full relative"
onClick={(e) => e.stopPropagation()}
>
{/* HEADER */}
<div className="bg-[#0F6C75] text-white px-5 py-4 flex flex-col gap-1 relative">
{/* Tombol close */}
<button
onClick={() => setOpenPreview(false)}
className="absolute top-3 right-4 text-white/80 hover:text-white text-lg"
>
</button>
<h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */}
<div className="flex items-center gap-2 mt-1">
<span className="bg-yellow-100 text-yellow-800 text-xs font-medium px-3 py-1 rounded-full">
Menunggu
</span>
<span className="bg-white/20 text-white text-xs px-2 py-[1px] rounded-full">
1
</span>
</div>
</div>
{/* IMAGE PREVIEW */}
<div className="bg-[#f8fafc] p-4 flex justify-center items-center">
<img
src={previewImage ?? ""}
alt="Preview"
className="rounded-lg w-full h-auto object-contain"
/>
</div>
{/* FOOTER */}
<div className="border-t text-center py-3 bg-[#E3EFF4]">
<button
onClick={() => setOpenPreview(false)}
className="text-[#0F6C75] font-medium hover:underline"
>
Tutup
</button>
</div>
</div>
</div>
)}
</>
);
}

View File

@ -522,7 +522,7 @@ export default function ProductTable() {
</button> </button>
<h2 className="text-lg font-semibold">JAEC00 J7 AWD</h2> <h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p> <p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */} {/* Status badge */}

View File

@ -499,7 +499,7 @@ export default function PromotionTable() {
</button> </button>
<h2 className="text-lg font-semibold">JAEC00 J7 AWD</h2> <h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p> <p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */} {/* Status badge */}

View File

@ -0,0 +1,508 @@
"use client";
import {
BannerIcon,
CopyIcon,
CreateIconIon,
DeleteIcon,
DotsYIcon,
EyeIconMdi,
SearchIcon,
} from "@/components/icons";
import { close, error, loading, success, successToast } from "@/config/swal";
import { Article } from "@/types/globals";
import { convertDateFormat } from "@/utils/global";
import Link from "next/link";
import { Key, useCallback, useEffect, useState } from "react";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import Cookies from "js-cookie";
import {
deleteArticle,
getArticleByCategory,
getArticlePagination,
updateIsBannerArticle,
} from "@/service/article";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@/components/ui/table";
import CustomPagination from "../layout/custom-pagination";
import { EditBannerDialog } from "../form/banner-edit-dialog";
import { deleteProduct, getProductPagination } from "@/service/product";
import { CheckCheck, Plus } from "lucide-react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "../ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import { Label } from "../ui/label";
import { useRouter } from "next/navigation";
const columns = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Banner", uid: "isBanner" },
{ name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" },
{ name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" },
];
const columnsOtherRole = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" },
{ name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" },
];
// interface Category {
// id: number;
// title: string;
// }
export default function ServicesTable() {
const MySwal = withReactContent(Swal);
const username = Cookies.get("username");
const userId = Cookies.get("uie");
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<any[]>([]);
const [showData, setShowData] = useState("10");
const [search, setSearch] = useState("");
const [categories, setCategories] = useState<any>([]);
const [selectedCategories, setSelectedCategories] = useState<any>("");
const [startDateValue, setStartDateValue] = useState({
startDate: null,
endDate: null,
});
const [userLevelId, setUserLevelId] = useState<string | null>(null);
const router = useRouter();
// 🔹 Ambil userlevelId dari cookies
useEffect(() => {
const ulne = Cookies.get("ulne"); // contoh: "3"
setUserLevelId(ulne ?? null);
}, []);
useEffect(() => {
initState();
getCategories();
}, []);
async function getCategories() {
const res = await getArticleByCategory();
const data = res?.data?.data;
setCategories(data);
}
const initState = useCallback(async () => {
loading();
const req = {
limit: showData,
page: page,
search: search,
};
const res = await getProductPagination(req);
await getTableNumber(parseInt(showData), res.data?.data);
setTotalPage(res?.data?.meta?.totalPage);
close();
}, [page]);
const getTableNumber = async (limit: number, data: Article[]) => {
if (data) {
const startIndex = limit * (page - 1);
let iterate = 0;
const newData = data.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
setArticle(newData);
} else {
setArticle([]);
}
};
async function doDelete(id: any) {
// loading();
const resDelete = await deleteProduct(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
success("Berhasil Hapus");
initState();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
const handleBanner = async (id: number, status: boolean) => {
const res = await updateIsBannerArticle(id, status);
if (res?.error) {
error(res?.message);
return false;
}
initState();
};
const [openEditDialog, setOpenEditDialog] = useState(false);
const [selectedBanner, setSelectedBanner] = useState<any>(null);
const [openPreview, setOpenPreview] = useState(false);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const handleUpdateBanner = (data: any) => {
console.log("Updated banner data:", data);
// TODO: panggil API update di sini
// lalu refresh tabel
};
const handlePreview = (imgUrl: string) => {
setPreviewImage(imgUrl);
setOpenPreview(true);
};
const copyUrlArticle = async (id: number, slug: string) => {
const url =
`${window.location.protocol}//${window.location.host}` +
"/news/detail/" +
`${id}-${slug}`;
try {
await navigator.clipboard.writeText(url);
successToast("Success", "Article Copy to Clipboard");
setTimeout(() => {}, 1500);
} catch (err) {
("Failed to copy!");
}
};
const renderCell = useCallback(
(article: any, columnKey: Key) => {
const cellValue = article[columnKey as keyof any];
switch (columnKey) {
case "isPublish":
return (
// <Chip
// className="capitalize "
// color={statusColorMap[article.status]}
// size="lg"
// variant="flat"
// >
// <div className="flex flex-row items-center gap-2 justify-center">
// {article.status}
// </div>
// </Chip>
<p>{article.isPublish ? "Publish" : "Draft"}</p>
);
case "isBanner":
return <p>{article.isBanner ? "Ya" : "Tidak"}</p>;
case "createdAt":
return <p>{convertDateFormat(article.createdAt)}</p>;
case "category":
return (
<p>
{article?.categories?.map((list: any) => list.title).join(", ") +
" "}
</p>
);
case "actions":
return (
<div className="relative flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<DotsYIcon className="h-5 w-5 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={() => copyUrlArticle(article.id, article.slug)}
>
<CopyIcon className="mr-2 h-4 w-4" />
Copy Url Article
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`/admin/article/detail/${article.id}`}
className="flex items-center"
>
<EyeIconMdi className="mr-2 h-4 w-4" />
Detail
</Link>
</DropdownMenuItem>
{(username === "admin-mabes" ||
Number(userId) === article.createdById) && (
<DropdownMenuItem asChild>
<Link
href={`/admin/article/edit/${article.id}`}
className="flex items-center"
>
<CreateIconIon className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
)}
{username === "admin-mabes" && (
<DropdownMenuItem
onClick={() =>
handleBanner(article.id, !article.isBanner)
}
>
<BannerIcon className="mr-2 h-4 w-4" />
{article.isBanner
? "Hapus dari Banner"
: "Jadikan Banner"}
</DropdownMenuItem>
)}
{(username === "admin-mabes" ||
Number(userId) === article.createdById) && (
<DropdownMenuItem onClick={() => handleDelete(article.id)}>
<DeleteIcon className="mr-2 h-4 w-4 text-red-500" />
Delete
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
default:
return cellValue;
}
},
[article, page]
);
let typingTimer: NodeJS.Timeout;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
setPage(1);
initState();
}
return (
<>
<div className="py-3">
<div className="w-full overflow-x-auto ">
{/* Header */}
<Tabs defaultValue="program-sales">
<TabsList className="py-3 px-3 bg-[#1F6779] rounded-sm">
<TabsTrigger value="program-sales" className="px-3 py-3 ">
Program Services
</TabsTrigger>
<TabsTrigger value="after-sales" className="px-3 py-3 ">
After Sales
</TabsTrigger>
</TabsList>
{userLevelId !== "3" && (
<Link href={"/admin/product/create"}>
<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" />
Tambah Program Sales
</Button>
</Link>
)}
<TabsContent
value="program-sales"
className="shadow-sm border border-gray-200 "
>
<div className="bg-[#0F6C75] text-white text-lg rounded-t-sm px-6 py-3">
Daftar Services
</div>
{/* FOOTER PAGINATION */}
<div className="flex items-center justify-between px-6 py-3 border-t text-sm text-gray-600">
<p>
Menampilkan {article.length} dari {article.length} data
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Previous
</Button>
<p>
Halaman {page} dari {totalPage}
</p>
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === totalPage}
onClick={() => setPage(page + 1)}
>
Next
</Button>
</div>
</div>
</TabsContent>
<TabsContent
value="after-sales"
className="shadow-sm border border-gray-200 "
>
<div className="bg-[#0F6C75] text-white text-lg rounded-t-sm px-6 py-3">
Daftar After Sales
</div>
{/* FOOTER PAGINATION */}
<div className="flex items-center justify-between px-6 py-3 border-t text-sm text-gray-600">
<p>
Menampilkan {article.length} dari {article.length} data
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Previous
</Button>
<p>
Halaman {page} dari {totalPage}
</p>
<Button
variant="outline"
size="sm"
className="rounded-full px-3"
disabled={page === totalPage}
onClick={() => setPage(page + 1)}
>
Next
</Button>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
<EditBannerDialog
open={openEditDialog}
onOpenChange={setOpenEditDialog}
bannerData={selectedBanner}
onSubmit={handleUpdateBanner}
/>
{/* Preview Dialog */}
{openPreview && (
<div
className="fixed inset-0 flex items-center justify-center bg-black/50 z-50 p-4"
onClick={() => setOpenPreview(false)}
>
<div
className="bg-white rounded-xl overflow-hidden shadow-2xl max-w-md w-full relative"
onClick={(e) => e.stopPropagation()}
>
{/* HEADER */}
<div className="bg-[#0F6C75] text-white px-5 py-4 flex flex-col gap-1 relative">
{/* Tombol close */}
<button
onClick={() => setOpenPreview(false)}
className="absolute top-3 right-4 text-white/80 hover:text-white text-lg"
>
</button>
<h2 className="text-lg font-semibold">JAEC00 J7 SHS-P</h2>
<p className="text-sm text-white/90">DELICATE OFF-ROAD SUV</p>
{/* Status badge */}
<div className="flex items-center gap-2 mt-1">
<span className="bg-yellow-100 text-yellow-800 text-xs font-medium px-3 py-1 rounded-full">
Menunggu
</span>
<span className="bg-white/20 text-white text-xs px-2 py-[1px] rounded-full">
1
</span>
</div>
</div>
{/* IMAGE PREVIEW */}
<div className="bg-[#f8fafc] p-4 flex justify-center items-center">
<img
src={previewImage ?? ""}
alt="Preview"
className="rounded-lg w-full h-auto object-contain"
/>
</div>
{/* FOOTER */}
<div className="border-t text-center py-3 bg-[#E3EFF4]">
<button
onClick={() => setOpenPreview(false)}
className="text-[#0F6C75] font-medium hover:underline"
>
Tutup
</button>
</div>
</div>
</div>
)}
</>
);
}

66
components/ui/tabs.tsx Normal file
View File

@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }

33
package-lock.json generated
View File

@ -24,6 +24,7 @@
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.13",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"apexcharts": "^4.7.0", "apexcharts": "^4.7.0",
"axios": "^1.10.0", "axios": "^1.10.0",
@ -2678,6 +2679,35 @@
} }
} }
}, },
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
"integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-roving-focus": "1.1.11",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@ -3202,7 +3232,6 @@
"version": "19.2.7", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"dev": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@ -3211,7 +3240,7 @@
"version": "19.2.3", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true, "devOptional": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }

View File

@ -25,6 +25,7 @@
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.13",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"apexcharts": "^4.7.0", "apexcharts": "^4.7.0",
"axios": "^1.10.0", "axios": "^1.10.0",