update
This commit is contained in:
parent
8f557bfc7e
commit
63e0890e08
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ArticleTable from "@/components/table/article-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
|
|
@ -8,10 +8,25 @@ import { BannerDialog } from "@/components/form/banner-dialog";
|
|||
import Link from "next/link";
|
||||
import ProductTable from "@/components/table/product-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() {
|
||||
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) => {
|
||||
console.log("Banner Data:", data);
|
||||
};
|
||||
|
|
@ -26,12 +41,14 @@ export default function AgentPage() {
|
|||
</div>
|
||||
|
||||
<div className="dark:bg-[#18181b] rounded-xl p-3">
|
||||
<Link href={"/admin/agent/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 Agent
|
||||
</Button>
|
||||
</Link>
|
||||
{userLevelId !== "3" && (
|
||||
<Link href={"/admin/agent/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 Agent
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<AgentTable />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ArticleTable from "@/components/table/article-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
|
|
@ -10,12 +10,22 @@ import router from "next/router";
|
|||
import { useRouter } from "next/navigation";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
export default function BasicPage() {
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [userLevelId, setUserLevelId] = useState<string | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
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) => {
|
||||
try {
|
||||
const response = await createBanner(formData);
|
||||
|
|
@ -48,13 +58,15 @@ export default function BasicPage() {
|
|||
</div>
|
||||
|
||||
<div className="dark:bg-[#18181b] rounded-xl p-3">
|
||||
<Button
|
||||
className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Tambah Banner
|
||||
</Button>
|
||||
{userLevelId !== "3" && (
|
||||
<Button
|
||||
className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Tambah Banner
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ArticleTable />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,28 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
import Galery from "@/components/table/galery";
|
||||
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() {
|
||||
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 = () => {
|
||||
console.log("Submit galeri...");
|
||||
|
|
@ -25,14 +39,15 @@ export default function GaleryPage() {
|
|||
</div>
|
||||
|
||||
<div className="dark:bg-[#18181b] rounded-xl p-3">
|
||||
<Button
|
||||
className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Tambah Galeri Baru
|
||||
</Button>
|
||||
|
||||
{userLevelId !== "3" && (
|
||||
<Button
|
||||
className="bg-[#1F6779] text-white w-full lg:w-fit hover:bg-[#1a9bb5] flex items-center gap-2"
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Tambah Galeri Baru
|
||||
</Button>
|
||||
)}
|
||||
<Galery />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,29 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, 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 { useRouter } from "next/navigation";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
export default function ProductPage() {
|
||||
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) => {
|
||||
console.log("Banner Data:", data);
|
||||
|
|
@ -26,12 +40,14 @@ export default function ProductPage() {
|
|||
</div>
|
||||
|
||||
<div className="dark:bg-[#18181b] rounded-xl p-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 Product Baru
|
||||
</Button>
|
||||
</Link>
|
||||
{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 Product Baru
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<ProductTable />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ArticleTable from "@/components/table/article-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
|
|
@ -9,10 +9,25 @@ import Link from "next/link";
|
|||
import ProductTable from "@/components/table/product-table";
|
||||
import AgentTable from "@/components/table/agent-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() {
|
||||
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) => {
|
||||
console.log("Banner Data:", data);
|
||||
// TODO: kirim data ke API di sini
|
||||
|
|
@ -28,12 +43,14 @@ export default function PromotionPage() {
|
|||
</div>
|
||||
|
||||
<div className="dark:bg-[#18181b] rounded-xl p-3">
|
||||
<Link href={"/admin/promotion/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 Promo Baru
|
||||
</Button>
|
||||
</Link>
|
||||
{userLevelId !== "3" && (
|
||||
<Link href={"/admin/promotion/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 Promo Baru
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<PromotionTable />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
import Exterior from "@/components/landing-page/exterior";
|
||||
import FeaturesAndSpecifications from "@/components/landing-page/features-and-specifications";
|
||||
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 HeaderProductJ7Awd from "@/components/landing-page/header-product-j7-awd";
|
||||
import Interior from "@/components/landing-page/interior";
|
||||
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 ProductJ7Page() {
|
||||
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 />
|
||||
<HeaderProductJ7Awd />
|
||||
<Exterior />
|
||||
<Interior />
|
||||
<FeaturesAndSpecifications />
|
||||
<HeaderProductJ7Shs />
|
||||
<ExteriorShs />
|
||||
<InteriorShs />
|
||||
<FeaturesAndSpecificationsShs />
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -76,7 +76,9 @@ export default function ExteriorJ8Awd() {
|
|||
transition={{ duration: 0.6 }}
|
||||
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
|
||||
</motion.h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const featuresshs = [
|
|||
{
|
||||
title: "Rear view mirrors",
|
||||
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",
|
||||
},
|
||||
{
|
||||
|
|
@ -53,7 +53,7 @@ export default function ExteriorShs() {
|
|||
transition={{ duration: 0.6 }}
|
||||
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
|
||||
</motion.h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default function Exterior() {
|
|||
transition={{ duration: 0.6 }}
|
||||
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
|
||||
</motion.h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ export default function FeaturesAndSpecificationsJ8() {
|
|||
return (
|
||||
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
|
||||
<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>
|
||||
|
||||
<div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]">
|
||||
|
|
@ -39,7 +42,9 @@ export default function FeaturesAndSpecificationsJ8() {
|
|||
/>
|
||||
</div>
|
||||
<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
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default function FeaturesAndSpecificationsShs() {
|
|||
return (
|
||||
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
|
||||
<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>
|
||||
|
||||
<div className="relative w-full h-[300px] sm:h-[400px] md:h-[600px]">
|
||||
|
|
@ -38,7 +38,7 @@ export default function FeaturesAndSpecificationsShs() {
|
|||
/>
|
||||
</div>
|
||||
<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
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default function FeaturesAndSpecifications() {
|
|||
return (
|
||||
<section className="pt-10 px-4 sm:px-6 md:px-20 bg-white">
|
||||
<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
|
||||
</h2>
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export default function FeaturesAndSpecifications() {
|
|||
/>
|
||||
</div>
|
||||
<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
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useState } from "react";
|
|||
export default function HeaderAfterSalesServices() {
|
||||
const cars = [
|
||||
{
|
||||
title: "JAECOO J7 AWD",
|
||||
title: "JAECOO J7 SHS-PV-P",
|
||||
image: "/j7-awd-nobg.png",
|
||||
price: "Rp 549.000.000",
|
||||
oldPrice: "Rp 544.000.000",
|
||||
|
|
@ -18,7 +18,7 @@ export default function HeaderAfterSalesServices() {
|
|||
display: `14.8"`,
|
||||
},
|
||||
{
|
||||
title: "JAECOO J7 SHS",
|
||||
title: "JAECOO J5 EV",
|
||||
image: "/j7-shs-nobg.png",
|
||||
price: "Rp 599.000.000",
|
||||
oldPrice: "Rp 594.000.000",
|
||||
|
|
@ -28,7 +28,7 @@ export default function HeaderAfterSalesServices() {
|
|||
display: `14.8"`,
|
||||
},
|
||||
{
|
||||
title: "JAECOO J8 AWD",
|
||||
title: "JAECOO J8 SHS-P ARDIS",
|
||||
image: "/j8-awd-nobg.png",
|
||||
price: "Rp 812.000.000",
|
||||
oldPrice: "Rp 807.000.000",
|
||||
|
|
|
|||
|
|
@ -19,31 +19,36 @@ export default function HeaderPriceInformation() {
|
|||
const [open, setOpen] = useState(false);
|
||||
const cars = [
|
||||
{
|
||||
title: "JAECOO J7 AWD",
|
||||
title: "JAECOO J7 SHS-P",
|
||||
image: "/j7-awd-nobg.png",
|
||||
price: "Rp 549.000.000",
|
||||
oldPrice: "Rp 544.000.000",
|
||||
price: "Rp 509.900.000",
|
||||
oldPrice: "Rp 509.900.000",
|
||||
capacity: "18.3kWh",
|
||||
power: "-",
|
||||
torque: "310 N.m",
|
||||
wheels: `19"`,
|
||||
seats: "Leather",
|
||||
display: `14.8"`,
|
||||
},
|
||||
{
|
||||
title: "JAECOO J7 SHS",
|
||||
title: "JAECOO J5 EV",
|
||||
image: "/j7-shs-nobg.png",
|
||||
price: "Rp 599.000.000",
|
||||
oldPrice: "Rp 594.000.000",
|
||||
capacity: "18.3kWh",
|
||||
price: "Rp 299.900.000",
|
||||
oldPrice: "Rp 299.900.000",
|
||||
capacity: "60.9kWh",
|
||||
power: "155kW",
|
||||
torque: "288Nm",
|
||||
wheels: `19"`,
|
||||
seats: "Leather",
|
||||
display: `14.8"`,
|
||||
},
|
||||
{
|
||||
title: "JAECOO J8 AWD",
|
||||
title: "JAECOO J8 SHS-P ARDIS",
|
||||
image: "/j8-awd-nobg.png",
|
||||
price: "Rp 812.000.000",
|
||||
oldPrice: "Rp 807.000.000",
|
||||
capacity: "18.3kWh",
|
||||
oldPrice: "Rp 828.000.000",
|
||||
capacity: "34,46kWh",
|
||||
torque: "650Nm",
|
||||
wheels: `19"`,
|
||||
seats: "Leather",
|
||||
display: `14.8"`,
|
||||
|
|
@ -119,10 +124,10 @@ export default function HeaderPriceInformation() {
|
|||
</svg>
|
||||
</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{" "}
|
||||
{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="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2">
|
||||
<svg
|
||||
|
|
@ -158,8 +163,8 @@ export default function HeaderPriceInformation() {
|
|||
/>
|
||||
</svg>
|
||||
<div className="flex flex-col items-center justify-center text-center">
|
||||
<span className="font-bold">{car.wheels}</span>{" "}
|
||||
<span className="text-sm">Alloy Wheels</span>
|
||||
<span className="font-bold">{car.power}</span>{" "}
|
||||
<span className="text-sm">Max Power</span>
|
||||
</div>
|
||||
</div>
|
||||
<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"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 512 512"
|
||||
viewBox="0 0 48 48"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
// fill="none"
|
||||
// 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>
|
||||
<div className="flex flex-col items-center justify-center text-center ml-5">
|
||||
<span className="font-bold">{car.seats}</span>{" "}
|
||||
<span className="text-sm"> Seats</span>
|
||||
<span className="font-bold">{car.torque}</span>{" "}
|
||||
<span className="text-sm"> Torque</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#EAF7FF] p-5 rounded-md flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default function Header() {
|
|||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
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.p
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ export default function InteriorJ8Awd() {
|
|||
animate={inView ? { opacity: 1, y: 0 } : {}}
|
||||
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
|
||||
</motion.h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export default function InteriorShs() {
|
|||
animate={inView ? { opacity: 1, y: 0 } : {}}
|
||||
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
|
||||
</motion.h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ export default function Interior() {
|
|||
animate={inView ? { opacity: 1, y: 0 } : {}}
|
||||
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.div
|
||||
|
|
|
|||
|
|
@ -18,19 +18,19 @@ import { useState } from "react";
|
|||
const items = [
|
||||
{
|
||||
image: "/new-car2.png",
|
||||
title: "JAECOO J7 AWD",
|
||||
title: "JAECOO J7 SHS-P",
|
||||
description: "DELICATE OFF-ROAD SUV",
|
||||
link: "/product/j7-awd",
|
||||
},
|
||||
{
|
||||
image: "/new-car1.png",
|
||||
title: "JAECOO J7 SHS",
|
||||
title: "JAECOO J5 EV",
|
||||
description: "SUPER HYBRID SYSTEM = SUPER HEV + EV",
|
||||
link: "/product/j7-shs",
|
||||
},
|
||||
{
|
||||
image: "/new-car3.png",
|
||||
title: "JAECOO J8 AWD",
|
||||
title: "JAECOO J8 SHS-P ARDIS",
|
||||
description: "FIRST CLASS OFF-ROAD",
|
||||
link: "/product/j8-awd",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,19 +47,19 @@ export default function Navbar() {
|
|||
|
||||
const produkList = [
|
||||
{
|
||||
name: "JAECOO J5 EV",
|
||||
name: "JAECOO J7 SHS-P",
|
||||
img: "/j7awd.png",
|
||||
link: "/product/j7-shs-p",
|
||||
},
|
||||
{
|
||||
name: "JAECOO J5 EV",
|
||||
img: "/j7shs.png",
|
||||
link: "/product/j5-ev",
|
||||
},
|
||||
{
|
||||
name: "JAECOO J7 SHS",
|
||||
img: "/j7shs.png",
|
||||
link: "/product/j7-shs",
|
||||
},
|
||||
{
|
||||
name: "JAECOO J8 AWD",
|
||||
name: "JAECOO J8 SHS-P ARDIS",
|
||||
img: "/j8awd.png",
|
||||
link: "/product/j8-awd",
|
||||
link: "/product/j8-shs-ardis",
|
||||
},
|
||||
];
|
||||
const priceList = [
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const sidebarSections = [
|
|||
height="18"
|
||||
/>
|
||||
),
|
||||
link: "/admin/costumer-support",
|
||||
link: "/admin/costumer-service",
|
||||
},
|
||||
{
|
||||
title: "Manajemen User",
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default function DashboardContainer() {
|
|||
no: 3,
|
||||
tanggal: "09/11/2024",
|
||||
jenis: "Produk",
|
||||
judul: "JAECOO J7 AWD Update",
|
||||
judul: "JAECOO J7 SHS-P Update",
|
||||
status: "Disetujui",
|
||||
},
|
||||
{
|
||||
|
|
@ -131,12 +131,12 @@ export default function DashboardContainer() {
|
|||
const notifications = [
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ export default function AgentTable() {
|
|||
✕
|
||||
</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>
|
||||
|
||||
{/* Status badge */}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import {
|
|||
import CustomPagination from "../layout/custom-pagination";
|
||||
import { EditBannerDialog } from "../form/banner-edit-dialog";
|
||||
import { deleteBanner, getBannerData, updateBanner } from "@/service/banner";
|
||||
import { CheckCheck } from "lucide-react";
|
||||
import { CheckCheck, Eye } from "lucide-react";
|
||||
|
||||
const columns = [
|
||||
{ name: "No", uid: "no" },
|
||||
|
|
@ -170,8 +170,20 @@ export default function ArticleTable() {
|
|||
const [openEditDialog, setOpenEditDialog] = useState(false);
|
||||
const [selectedBanner, setSelectedBanner] = useState<any>(null);
|
||||
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 handleOpenApproverHistory = () => {
|
||||
setOpenApproverHistory(true);
|
||||
};
|
||||
|
||||
const handleEdit = (item: any) => {
|
||||
setSelectedBanner({
|
||||
id: item.id,
|
||||
|
|
@ -211,19 +223,7 @@ export default function ArticleTable() {
|
|||
|
||||
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>
|
||||
);
|
||||
return <p>{article.isPublish ? "Publish" : "Draft"}</p>;
|
||||
case "isBanner":
|
||||
return <p>{article.isBanner ? "Ya" : "Tidak"}</p>;
|
||||
case "createdAt":
|
||||
|
|
@ -424,10 +424,10 @@ export default function ArticleTable() {
|
|||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#0F6C75] hover:bg-transparent hover:underline p-0"
|
||||
// onClick={() => handleEdit(item)}
|
||||
onClick={() => handleView(item)}
|
||||
>
|
||||
<CheckCheck className="w-4 h-4 mr-1" />
|
||||
Approve
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
Lihat
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -521,7 +521,7 @@ export default function ArticleTable() {
|
|||
✕
|
||||
</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>
|
||||
|
||||
{/* Status badge */}
|
||||
|
|
@ -556,6 +556,240 @@ export default function ArticleTable() {
|
|||
</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>></span>
|
||||
<span>></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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -522,7 +522,7 @@ export default function ProductTable() {
|
|||
✕
|
||||
</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>
|
||||
|
||||
{/* Status badge */}
|
||||
|
|
|
|||
|
|
@ -499,7 +499,7 @@ export default function PromotionTable() {
|
|||
✕
|
||||
</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>
|
||||
|
||||
{/* Status badge */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"apexcharts": "^4.7.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": {
|
||||
"version": "1.1.1",
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
|
|
@ -3211,7 +3240,7 @@
|
|||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"apexcharts": "^4.7.0",
|
||||
"axios": "^1.10.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue