Merge branch 'main' of https://gitlab.com/hanifsalafi/mediahub_redesign into prod
This commit is contained in:
commit
3d53652765
|
|
@ -762,13 +762,13 @@ const FilterPage = () => {
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{audioData?.map((audio: any) => (
|
{audioData?.map((audio: any) => (
|
||||||
<div key={audio?.id}>
|
<div key={audio?.id}>
|
||||||
<div
|
<Link
|
||||||
// href={`/audio/detail/${audio?.slug}`}
|
href={prefixPath + `/audio/detail/${audio?.slug}`}
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
router.push(
|
// router.push(
|
||||||
prefixPath + `/audio/detail/${audio?.slug}`
|
// prefixPath + `/audio/detail/${audio?.slug}`
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
className="flex flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
className="flex flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||||
>
|
>
|
||||||
<div className="flex justify-center lg:justify-between w-full gap-2 lg:gap-6">
|
<div className="flex justify-center lg:justify-between w-full gap-2 lg:gap-6">
|
||||||
|
|
@ -843,7 +843,7 @@ const FilterPage = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -827,13 +827,13 @@ const FilterPage = () => {
|
||||||
{documentData?.length > 0 ? (
|
{documentData?.length > 0 ? (
|
||||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{documentData?.map((document: any) => (
|
{documentData?.map((document: any) => (
|
||||||
<div
|
<Link
|
||||||
// href={`/document/detail/${document?.slug}`}
|
href={ prefixPath + `/document/detail/${document?.slug}`}
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
router.push(
|
// router.push(
|
||||||
prefixPath + `/document/detail/${document?.slug}`
|
// prefixPath + `/document/detail/${document?.slug}`
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
key={document?.id}
|
key={document?.id}
|
||||||
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||||
>
|
>
|
||||||
|
|
@ -879,7 +879,7 @@ const FilterPage = () => {
|
||||||
Download Dokumen
|
Download Dokumen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -815,13 +815,13 @@ const FilterPage = () => {
|
||||||
className="hover:scale-105 transition-transform duration-300"
|
className="hover:scale-105 transition-transform duration-300"
|
||||||
>
|
>
|
||||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||||
<div
|
<Link
|
||||||
// href={`/image/detail/${image?.slug}`}
|
href={ prefixPath + `/image/detail/${image?.slug}`}
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
router.push(
|
// router.push(
|
||||||
prefixPath + `/image/detail/${image?.slug}`
|
// prefixPath + `/image/detail/${image?.slug}`
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
>
|
>
|
||||||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||||
|
|
@ -854,7 +854,7 @@ const FilterPage = () => {
|
||||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||||
{image?.title}
|
{image?.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,344 +1,476 @@
|
||||||
"use client";
|
// "use client";
|
||||||
|
|
||||||
import { useParams, usePathname } from "next/navigation";
|
// import { useParams, usePathname } from "next/navigation";
|
||||||
|
// import React, { useEffect, useState } from "react";
|
||||||
|
// import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
// import {
|
||||||
|
// checkWishlistStatus,
|
||||||
|
// deleteWishlist,
|
||||||
|
// getDetail,
|
||||||
|
// saveWishlist,
|
||||||
|
// } from "@/service/landing/landing";
|
||||||
|
// import VideoPlayer from "@/utils/video-player";
|
||||||
|
// import NewContent from "@/components/landing-page/new-content";
|
||||||
|
// import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
// import { Textarea } from "@/components/ui/textarea";
|
||||||
|
// import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
// import { close, error, loading } from "@/config/swal";
|
||||||
|
// import { useToast } from "@/components/ui/use-toast";
|
||||||
|
// import { postActivityLog } from "@/service/content/content";
|
||||||
|
|
||||||
|
// const DetailVideo = () => {
|
||||||
|
// const [selectedSize, setSelectedSize] = useState<string>("L");
|
||||||
|
// const [selectedTab, setSelectedTab] = useState("video");
|
||||||
|
// const router = useRouter();
|
||||||
|
// const pathname = usePathname();
|
||||||
|
// const params = useParams();
|
||||||
|
// const slug = String(params?.slug);
|
||||||
|
// const [detailDataVideo, setDetailDataVideo] = useState<any>();
|
||||||
|
// const [isSaved, setIsSaved] = useState(false);
|
||||||
|
// const [wishlistId, setWishlistId] = useState();
|
||||||
|
// const { toast } = useToast();
|
||||||
|
// const [isDownloadAll, setIsDownloadAll] = useState(false);
|
||||||
|
// const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
|
// const [isFromSPIT, setIsFromSPIT] = useState(false);
|
||||||
|
// const [main, setMain] = useState<any>();
|
||||||
|
// const [resolutionSelected, setResolutionSelected] = useState("720");
|
||||||
|
// const [selectedVideo, setSelectedVideo] = useState(0);
|
||||||
|
|
||||||
|
// const userId = getCookiesDecrypt("uie");
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// initFetch();
|
||||||
|
// checkWishlist();
|
||||||
|
// sendActivityLog(2);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// const initFetch = async () => {
|
||||||
|
// const response = await getDetail(String(slug));
|
||||||
|
// console.log("detailVideo", response);
|
||||||
|
// setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
||||||
|
// setMain({
|
||||||
|
// id: response?.data?.data?.files[0]?.id,
|
||||||
|
// type: response?.data?.data?.fileType.name,
|
||||||
|
// url:
|
||||||
|
// Number(response?.data?.data?.fileType?.id) == 4
|
||||||
|
// ? response?.data?.data?.files[0]?.secondaryUrl
|
||||||
|
// : Number(response?.data?.data?.fileType?.id) == 2
|
||||||
|
// ? `${process.env.NEXT_PUBLIC_API}/media/view?id=${response?.data?.data?.files[0]?.id}&operation=file&type=video`
|
||||||
|
// : response?.data?.data?.files[0]?.url,
|
||||||
|
// thumbnailFileUrl: response?.data?.data?.files[0]?.thumbnailFileUrl,
|
||||||
|
// names: response?.data?.data?.files[0]?.fileName,
|
||||||
|
// format: response?.data?.data?.files[0]?.format,
|
||||||
|
// widthPixel: response?.data?.data?.files[0]?.widthPixel,
|
||||||
|
// heightPixel: response?.data?.data?.files[0]?.heightPixel,
|
||||||
|
// size: response?.data?.data?.files[0]?.size,
|
||||||
|
// caption: response?.data?.data?.files[0]?.caption,
|
||||||
|
// });
|
||||||
|
// setDetailDataVideo(response?.data?.data);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const doBookmark = async () => {
|
||||||
|
// if (userId) {
|
||||||
|
// const data = {
|
||||||
|
// mediaUploadId: slug?.split("-")?.[0],
|
||||||
|
// };
|
||||||
|
|
||||||
|
// loading();
|
||||||
|
// const res = await saveWishlist(data);
|
||||||
|
// if (res?.error) {
|
||||||
|
// error(res.message);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// close();
|
||||||
|
// toast({
|
||||||
|
// title: "Konten berhasil disimpan",
|
||||||
|
// });
|
||||||
|
// checkWishlist();
|
||||||
|
// } else {
|
||||||
|
// router.push("/auth");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// async function checkWishlist() {
|
||||||
|
// if (userId) {
|
||||||
|
// const res = await checkWishlistStatus(slug.split("-")?.[0]);
|
||||||
|
// console.log(res?.data?.data);
|
||||||
|
// const isAlreadyOnWishlist = res?.data?.data !== "-1";
|
||||||
|
// setWishlistId(res?.data?.data);
|
||||||
|
// setIsSaved(isAlreadyOnWishlist);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const handleDeleteWishlist = async () => {
|
||||||
|
// if (userId) {
|
||||||
|
// loading();
|
||||||
|
// const res = await deleteWishlist(wishlistId);
|
||||||
|
|
||||||
|
// if (res?.error) {
|
||||||
|
// error(res.message);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// toast({
|
||||||
|
// title: "Konten berhasil dihapus",
|
||||||
|
// });
|
||||||
|
// close();
|
||||||
|
// checkWishlist();
|
||||||
|
// } else {
|
||||||
|
// router.push("/auth");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const sizes = [
|
||||||
|
// { label: "XL", value: "3198 x 1798 px" },
|
||||||
|
// { label: "L", value: "2399 x 1349 px" },
|
||||||
|
// { label: "M", value: "1599 x 899 px" },
|
||||||
|
// { label: "S", value: "1066 x 599 px" },
|
||||||
|
// { label: "XS", value: "800 x 450 px" },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// async function sendActivityLog(activityTypeId: number) {
|
||||||
|
// const data = {
|
||||||
|
// activityTypeId,
|
||||||
|
// mediaId: slug.split("-")?.[0],
|
||||||
|
// url: window.location.href,
|
||||||
|
// };
|
||||||
|
// // set activity
|
||||||
|
// await postActivityLog(data);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const handleDownload = () => {
|
||||||
|
// if (downloadProgress === 0) {
|
||||||
|
// if (!userId) {
|
||||||
|
// router.push("/auth");
|
||||||
|
// } else {
|
||||||
|
// sendActivityLog(2);
|
||||||
|
// sendActivityLog(3);
|
||||||
|
|
||||||
|
// if (isDownloadAll) {
|
||||||
|
// let url: string;
|
||||||
|
// const baseId = slug.split("-")?.[0];
|
||||||
|
|
||||||
|
// // if (type === "1") {
|
||||||
|
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${resolutionSelected}`;
|
||||||
|
// // } else if (type === "2") {
|
||||||
|
// // url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${imageSizeSelected}`;
|
||||||
|
// // } else {
|
||||||
|
// // url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}`;
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// downloadFile(url, "FileDownload.zip");
|
||||||
|
// } else {
|
||||||
|
// if (isFromSPIT && main?.url?.includes("spit.humas")) {
|
||||||
|
// downloadFile(`${main?.url}`, `${main.names}`);
|
||||||
|
// } else {
|
||||||
|
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
|
||||||
|
// downloadFile(url, `${main.names}`);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // } else if (type === "1" && resolutionSelected?.length > 0) {
|
||||||
|
// // if (isFromSPIT && main?.url?.includes("spit.humas")) {
|
||||||
|
// // downloadFile(`${main?.url}`, `${main.names}`);
|
||||||
|
// // } else {
|
||||||
|
// // const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
|
||||||
|
// // downloadFile(url, `${main.names}`);
|
||||||
|
// // }
|
||||||
|
// // } else if (type === "2" && imageSizeSelected?.length > 0) {
|
||||||
|
// // const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
|
||||||
|
// // downloadFile(url, `${main.names}`);
|
||||||
|
// // } else if (type === "3" || type === "4") {
|
||||||
|
// // downloadFile(`${main?.url}`, `${main.names}`);
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const downloadFile = (fileUrl: string, name: string) => {
|
||||||
|
// const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
// xhr.open("GET", fileUrl, true);
|
||||||
|
// xhr.responseType = "blob";
|
||||||
|
|
||||||
|
// xhr.addEventListener("progress", (event) => {
|
||||||
|
// if (event.lengthComputable) {
|
||||||
|
// const percentCompleted = Math.round((event.loaded / event.total) * 100);
|
||||||
|
// setDownloadProgress(percentCompleted);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// xhr.addEventListener("readystatechange", () => {
|
||||||
|
// if (xhr.readyState === 4 && xhr.status === 200) {
|
||||||
|
// const contentType =
|
||||||
|
// xhr.getResponseHeader("content-type") || "application/octet-stream";
|
||||||
|
// const extension = contentType.split("/")[1];
|
||||||
|
// const filename = `${name}.${extension}`;
|
||||||
|
|
||||||
|
// const blob = new Blob([xhr.response], {
|
||||||
|
// type: contentType,
|
||||||
|
// });
|
||||||
|
// const downloadUrl = URL.createObjectURL(blob);
|
||||||
|
// const a = document.createElement("a");
|
||||||
|
|
||||||
|
// a.href = downloadUrl;
|
||||||
|
// a.download = filename;
|
||||||
|
// document.body.append(a);
|
||||||
|
// a.click();
|
||||||
|
// a.remove();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// xhr.onloadend = () => {
|
||||||
|
// setDownloadProgress(0);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// xhr.send();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// <div className="px-4 md:px-24 py-4">
|
||||||
|
// {/* Container Utama */}
|
||||||
|
// <div className="rounded-md overflow-hidden md:flex">
|
||||||
|
// {/* Bagian Kiri */}
|
||||||
|
// <div className="md:w-3/4">
|
||||||
|
// {/* <div className="relative">
|
||||||
|
// <VideoPlayer url={detailDataVideo?.files[0]?.url} />
|
||||||
|
// <div className="absolute top-4 left-4"></div>
|
||||||
|
// </div> */}
|
||||||
|
// <div className="relative max-h-screen overflow-hidden">
|
||||||
|
// <div className="w-full max-h-screen aspect-video">
|
||||||
|
// <div className="w-full h-full object-contain">
|
||||||
|
// <VideoPlayer
|
||||||
|
// url={detailDataVideo?.files[selectedVideo]?.url}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="absolute top-4 left-4"></div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="py-4 flex flex-row gap-3">
|
||||||
|
// {detailDataVideo?.files?.map((file: any, index: number) => (
|
||||||
|
// <div
|
||||||
|
// key={file?.id}
|
||||||
|
// onClick={() => setSelectedVideo(index)}
|
||||||
|
// className="cursor-pointer flex flex-col items-center gap-1"
|
||||||
|
// >
|
||||||
|
// <img
|
||||||
|
// src={file?.thumbnailFileUrl}
|
||||||
|
// alt={file?.fileName}
|
||||||
|
// className="w-32 h-20 object-cover rounded"
|
||||||
|
// />
|
||||||
|
// {/* <p className="text-sm text-center">{file?.fileName}</p> */}
|
||||||
|
// </div>
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Footer Informasi */}
|
||||||
|
// <div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||||
|
// <p className="flex flex-row items-center mt-3">
|
||||||
|
// oleh
|
||||||
|
// <span className="font-semibold text-black">
|
||||||
|
// {detailDataVideo?.uploadedBy?.userLevel?.name}
|
||||||
|
// </span>
|
||||||
|
// | Diupdate pada {detailDataVideo?.updatedAt}{" "}
|
||||||
|
// WIB |
|
||||||
|
// <Icon icon="formkit:eye" width="15" height="15" />
|
||||||
|
//
|
||||||
|
// {detailDataVideo?.clickCount}
|
||||||
|
// </p>
|
||||||
|
// <p className="mt-3">Kreator: {detailDataVideo?.creatorName}</p>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Keterangan */}
|
||||||
|
// <div className="w-full">
|
||||||
|
// <h1 className="flex flex-row font-bold text-2xl my-8">
|
||||||
|
// {detailDataVideo?.title}
|
||||||
|
// </h1>
|
||||||
|
// <div
|
||||||
|
// className="font-light text-justify"
|
||||||
|
// dangerouslySetInnerHTML={{
|
||||||
|
// __html: detailDataVideo?.htmlDescription,
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Bagian Kanan */}
|
||||||
|
// <div className="md:w-1/4 p-4 bg-[#f7f7f7] rounded-lg mx-4 h-fit">
|
||||||
|
// {isSaved ? (
|
||||||
|
// <a
|
||||||
|
// onClick={() => handleDeleteWishlist()}
|
||||||
|
// className="flex flex-col mb-3 items-center justify-center cursor-pointer"
|
||||||
|
// >
|
||||||
|
// <Icon icon="material-symbols:bookmark" width={40} />
|
||||||
|
// <p className="text-base lg:text-lg">Hapus</p>
|
||||||
|
// </a>
|
||||||
|
// ) : (
|
||||||
|
// <a
|
||||||
|
// onClick={() => doBookmark()}
|
||||||
|
// className="flex flex-col mb-3 items-center justify-center cursor-pointer"
|
||||||
|
// >
|
||||||
|
// <Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||||
|
// <p className="text-base lg:text-lg">Simpan</p>
|
||||||
|
// </a>
|
||||||
|
// )}
|
||||||
|
|
||||||
|
// {/* garis */}
|
||||||
|
// <div className="border-t border-black my-4"></div>
|
||||||
|
|
||||||
|
// <Link
|
||||||
|
// href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`}
|
||||||
|
// className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"
|
||||||
|
// >
|
||||||
|
// {detailDataVideo?.categoryName}
|
||||||
|
// </Link>
|
||||||
|
|
||||||
|
// <div className="flex justify-center flex-wrap gap-2 mb-4">
|
||||||
|
// {detailDataVideo?.tags.split(",").map((tag: string) => (
|
||||||
|
// <a
|
||||||
|
// onClick={() => router.push(`/all/filter?tag=${tag}`)}
|
||||||
|
// key={tag}
|
||||||
|
// className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white"
|
||||||
|
// >
|
||||||
|
// {tag}
|
||||||
|
// </a>
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="border-t border-black my-4"></div>
|
||||||
|
|
||||||
|
// {/* Opsi Ukuran Foto */}
|
||||||
|
// <h4 className="flex text-lg justify-center items-center font-semibold my-3">
|
||||||
|
// Opsi Ukuran Audio Visual
|
||||||
|
// </h4>
|
||||||
|
|
||||||
|
// <div className="border-t border-black my-4"></div>
|
||||||
|
|
||||||
|
// <div className="space-y-2">
|
||||||
|
// {sizes.map((size: any) => (
|
||||||
|
// <div className="flex flex-row justify-between">
|
||||||
|
// <div
|
||||||
|
// key={size.label}
|
||||||
|
// className="items-center flex flex-row gap-2 cursor-pointer"
|
||||||
|
// >
|
||||||
|
// <input
|
||||||
|
// type="radio"
|
||||||
|
// name="size"
|
||||||
|
// value={size.label}
|
||||||
|
// checked={selectedSize === size.label}
|
||||||
|
// onChange={() => setSelectedSize(size.label)}
|
||||||
|
// className="text-red-600 focus:ring-red-600"
|
||||||
|
// />
|
||||||
|
// <div className="text-sm">{size.label}</div>
|
||||||
|
// </div>
|
||||||
|
// <div className="">
|
||||||
|
// <div className="text-sm">{size.value}</div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Download Semua */}
|
||||||
|
// <div className="mt-4">
|
||||||
|
// <label className="flex items-center space-x-2 text-sm">
|
||||||
|
// <input
|
||||||
|
// type="checkbox"
|
||||||
|
// className="text-red-600 focus:ring-red-600"
|
||||||
|
// onChange={() => setIsDownloadAll(!isDownloadAll)}
|
||||||
|
// />
|
||||||
|
// <span>Download Semua File?</span>
|
||||||
|
// </label>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Tombol Download */}
|
||||||
|
// <button
|
||||||
|
// onClick={handleDownload}
|
||||||
|
// className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"
|
||||||
|
// >
|
||||||
|
// <svg
|
||||||
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
|
// width="1em"
|
||||||
|
// height="1em"
|
||||||
|
// viewBox="0 0 24 24"
|
||||||
|
// >
|
||||||
|
// <path
|
||||||
|
// fill="white"
|
||||||
|
// d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
|
||||||
|
// />
|
||||||
|
// </svg>
|
||||||
|
// Download
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="w-full mb-8">
|
||||||
|
// {/* Comment */}
|
||||||
|
// <div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
|
||||||
|
// <div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||||
|
// <p className="flex items-start text-lg">Berikan Komentar</p>
|
||||||
|
// <Textarea
|
||||||
|
// placeholder="Type your comments here."
|
||||||
|
// className="flex w-full"
|
||||||
|
// />
|
||||||
|
// <button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">
|
||||||
|
// Kirim
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Konten Serupa */}
|
||||||
|
// <div className="px-4 lg:px-24">
|
||||||
|
// <NewContent group="polda" type={"similar"} />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default DetailVideo;
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import {
|
||||||
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
|
checkWishlistStatus,
|
||||||
import VideoPlayer from "@/utils/video-player";
|
createPublicSuggestion,
|
||||||
import NewContent from "@/components/landing-page/new-content";
|
deletePublicSuggestion,
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
deleteWishlist,
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
getDetail,
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
getDetailMetaData,
|
||||||
import { close, error, loading } from "@/config/swal";
|
getPublicSuggestionList,
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
saveWishlist,
|
||||||
import { postActivityLog } from "@/service/content/content";
|
} from "@/service/landing/landing";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import DetailImage from "@/components/main/image-detail";
|
||||||
|
import DetailVideo from "@/components/main/video-detail";
|
||||||
|
|
||||||
const DetailVideo = () => {
|
interface Size {
|
||||||
const [selectedSize, setSelectedSize] = useState<string>("L");
|
label: string;
|
||||||
const [selectedTab, setSelectedTab] = useState("video");
|
value: string;
|
||||||
const router = useRouter();
|
}
|
||||||
const pathname = usePathname();
|
|
||||||
const params = useParams();
|
|
||||||
const slug = String(params?.slug);
|
|
||||||
const [detailDataVideo, setDetailDataVideo] = useState<any>();
|
|
||||||
const [isSaved, setIsSaved] = useState(false);
|
|
||||||
const [wishlistId, setWishlistId] = useState();
|
|
||||||
const { toast } = useToast();
|
|
||||||
const [isDownloadAll, setIsDownloadAll] = useState(false);
|
|
||||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
|
||||||
const [isFromSPIT, setIsFromSPIT] = useState(false);
|
|
||||||
const [main, setMain] = useState<any>();
|
|
||||||
const [resolutionSelected, setResolutionSelected] = useState("720");
|
|
||||||
|
|
||||||
const userId = getCookiesDecrypt("uie");
|
type Props = {
|
||||||
|
params: {
|
||||||
useEffect(() => {
|
title: string;
|
||||||
initFetch();
|
slug: string;
|
||||||
checkWishlist();
|
description: string;
|
||||||
sendActivityLog(2);
|
thumbnailLink: string;
|
||||||
}, []);
|
|
||||||
|
|
||||||
const initFetch = async () => {
|
|
||||||
const response = await getDetail(String(slug));
|
|
||||||
// console.log("detailVideo", response);
|
|
||||||
setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
|
||||||
setMain({
|
|
||||||
id: response?.data?.data?.files[0]?.id,
|
|
||||||
type: response?.data?.data?.fileType.name,
|
|
||||||
url:
|
|
||||||
Number(response?.data?.data?.fileType?.id) == 4
|
|
||||||
? response?.data?.data?.files[0]?.secondaryUrl
|
|
||||||
: Number(response?.data?.data?.fileType?.id) == 2
|
|
||||||
? `${process.env.NEXT_PUBLIC_API}/media/view?id=${response?.data?.data?.files[0]?.id}&operation=file&type=video`
|
|
||||||
: response?.data?.data?.files[0]?.url,
|
|
||||||
thumbnailFileUrl: response?.data?.data?.files[0]?.thumbnailFileUrl,
|
|
||||||
names: response?.data?.data?.files[0]?.fileName,
|
|
||||||
format: response?.data?.data?.files[0]?.format,
|
|
||||||
widthPixel: response?.data?.data?.files[0]?.widthPixel,
|
|
||||||
heightPixel: response?.data?.data?.files[0]?.heightPixel,
|
|
||||||
size: response?.data?.data?.files[0]?.size,
|
|
||||||
caption: response?.data?.data?.files[0]?.caption,
|
|
||||||
});
|
|
||||||
setDetailDataVideo(response?.data?.data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const doBookmark = async () => {
|
|
||||||
if (userId) {
|
|
||||||
const data = {
|
|
||||||
mediaUploadId: slug?.split("-")?.[0],
|
|
||||||
};
|
|
||||||
|
|
||||||
loading();
|
|
||||||
const res = await saveWishlist(data);
|
|
||||||
if (res?.error) {
|
|
||||||
error(res.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
toast({
|
|
||||||
title: "Konten berhasil disimpan",
|
|
||||||
});
|
|
||||||
checkWishlist();
|
|
||||||
} else {
|
|
||||||
router.push("/auth");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
async function checkWishlist() {
|
|
||||||
if (userId) {
|
|
||||||
const res = await checkWishlistStatus(slug.split("-")?.[0]);
|
|
||||||
// console.log(res?.data?.data);
|
|
||||||
const isAlreadyOnWishlist = res?.data?.data !== "-1";
|
|
||||||
setWishlistId(res?.data?.data);
|
|
||||||
setIsSaved(isAlreadyOnWishlist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteWishlist = async () => {
|
|
||||||
if (userId) {
|
|
||||||
loading();
|
|
||||||
const res = await deleteWishlist(wishlistId);
|
|
||||||
|
|
||||||
if (res?.error) {
|
|
||||||
error(res.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Konten berhasil dihapus",
|
|
||||||
});
|
|
||||||
close();
|
|
||||||
checkWishlist();
|
|
||||||
} else {
|
|
||||||
router.push("/auth");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sizes = [
|
|
||||||
{ label: "XL", value: "3198 x 1798 px" },
|
|
||||||
{ label: "L", value: "2399 x 1349 px" },
|
|
||||||
{ label: "M", value: "1599 x 899 px" },
|
|
||||||
{ label: "S", value: "1066 x 599 px" },
|
|
||||||
{ label: "XS", value: "800 x 450 px" },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function sendActivityLog(activityTypeId: number) {
|
|
||||||
const data = {
|
|
||||||
activityTypeId,
|
|
||||||
mediaId: slug.split("-")?.[0],
|
|
||||||
url: window.location.href,
|
|
||||||
};
|
|
||||||
// set activity
|
|
||||||
await postActivityLog(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
if (downloadProgress === 0) {
|
|
||||||
if (!userId) {
|
|
||||||
router.push("/auth");
|
|
||||||
} else {
|
|
||||||
sendActivityLog(2);
|
|
||||||
sendActivityLog(3);
|
|
||||||
|
|
||||||
if (isDownloadAll) {
|
|
||||||
let url: string;
|
|
||||||
const baseId = slug.split("-")?.[0];
|
|
||||||
|
|
||||||
// if (type === "1") {
|
|
||||||
url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${resolutionSelected}`;
|
|
||||||
// } else if (type === "2") {
|
|
||||||
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${imageSizeSelected}`;
|
|
||||||
// } else {
|
|
||||||
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
downloadFile(url, "FileDownload.zip");
|
|
||||||
} else {
|
|
||||||
if (isFromSPIT && main?.url?.includes("spit.humas")) {
|
|
||||||
downloadFile(`${main?.url}`, `${main.names}`);
|
|
||||||
} else {
|
|
||||||
const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
|
|
||||||
downloadFile(url, `${main.names}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// } else if (type === "1" && resolutionSelected?.length > 0) {
|
|
||||||
// if (isFromSPIT && main?.url?.includes("spit.humas")) {
|
|
||||||
// downloadFile(`${main?.url}`, `${main.names}`);
|
|
||||||
// } else {
|
|
||||||
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
|
|
||||||
// downloadFile(url, `${main.names}`);
|
|
||||||
// }
|
|
||||||
// } else if (type === "2" && imageSizeSelected?.length > 0) {
|
|
||||||
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
|
|
||||||
// downloadFile(url, `${main.names}`);
|
|
||||||
// } else if (type === "3" || type === "4") {
|
|
||||||
// downloadFile(`${main?.url}`, `${main.names}`);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadFile = (fileUrl: string, name: string) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.open("GET", fileUrl, true);
|
|
||||||
xhr.responseType = "blob";
|
|
||||||
|
|
||||||
xhr.addEventListener("progress", (event) => {
|
|
||||||
if (event.lengthComputable) {
|
|
||||||
const percentCompleted = Math.round((event.loaded / event.total) * 100);
|
|
||||||
setDownloadProgress(percentCompleted);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.addEventListener("readystatechange", () => {
|
|
||||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
||||||
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
|
|
||||||
const extension = contentType.split("/")[1];
|
|
||||||
const filename = `${name}.${extension}`;
|
|
||||||
|
|
||||||
const blob = new Blob([xhr.response], {
|
|
||||||
type: contentType,
|
|
||||||
});
|
|
||||||
const downloadUrl = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
|
||||||
|
|
||||||
a.href = downloadUrl;
|
|
||||||
a.download = filename;
|
|
||||||
document.body.append(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.onloadend = () => {
|
|
||||||
setDownloadProgress(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="px-4 md:px-24 py-4">
|
|
||||||
{/* Container Utama */}
|
|
||||||
<div className="rounded-md overflow-hidden md:flex">
|
|
||||||
{/* Bagian Kiri */}
|
|
||||||
<div className="md:w-3/4">
|
|
||||||
<div className="relative">
|
|
||||||
<VideoPlayer url={detailDataVideo?.files[0]?.url} />
|
|
||||||
<div className="absolute top-4 left-4"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer Informasi */}
|
|
||||||
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
|
||||||
<p className="flex flex-row items-center mt-3">
|
|
||||||
oleh
|
|
||||||
<span className="font-semibold text-black">{detailDataVideo?.uploadedBy?.userLevel?.name}</span>
|
|
||||||
| Diupdate pada {detailDataVideo?.updatedAt} WIB |
|
|
||||||
<Icon icon="formkit:eye" width="15" height="15" />
|
|
||||||
|
|
||||||
{detailDataVideo?.clickCount}
|
|
||||||
</p>
|
|
||||||
<p className="mt-3">Kreator: {detailDataVideo?.creatorName}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Keterangan */}
|
|
||||||
<div className="w-full">
|
|
||||||
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataVideo?.title}</h1>
|
|
||||||
<div
|
|
||||||
className="font-light text-justify"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: detailDataVideo?.htmlDescription,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bagian Kanan */}
|
|
||||||
<div className="md:w-1/4 p-4 bg-[#f7f7f7] rounded-lg mx-4 h-fit">
|
|
||||||
{isSaved ? (
|
|
||||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
|
||||||
<Icon icon="material-symbols:bookmark" width={40} />
|
|
||||||
<p className="text-base lg:text-lg">Hapus</p>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
|
||||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
|
||||||
<p className="text-base lg:text-lg">Simpan</p>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* garis */}
|
|
||||||
<div className="border-t border-black my-4"></div>
|
|
||||||
|
|
||||||
<Link href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
|
|
||||||
{detailDataVideo?.categoryName}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="flex justify-center flex-wrap gap-2 mb-4">
|
|
||||||
{detailDataVideo?.tags.split(",").map((tag: string) => (
|
|
||||||
<a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white">
|
|
||||||
{tag}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-black my-4"></div>
|
|
||||||
|
|
||||||
{/* Opsi Ukuran Foto */}
|
|
||||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio Visual</h4>
|
|
||||||
|
|
||||||
<div className="border-t border-black my-4"></div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{sizes.map((size: any) => (
|
|
||||||
<div className="flex flex-row justify-between">
|
|
||||||
<div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer">
|
|
||||||
<input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" />
|
|
||||||
<div className="text-sm">{size.label}</div>
|
|
||||||
</div>
|
|
||||||
<div className="">
|
|
||||||
<div className="text-sm">{size.value}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Download Semua */}
|
|
||||||
<div className="mt-4">
|
|
||||||
<label className="flex items-center space-x-2 text-sm">
|
|
||||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
|
||||||
<span>Download Semua File?</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tombol Download */}
|
|
||||||
<button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
|
||||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
|
||||||
</svg>
|
|
||||||
Download
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full mb-8">
|
|
||||||
{/* Comment */}
|
|
||||||
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
|
|
||||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
|
||||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
|
||||||
<Textarea placeholder="Type your comments here." className="flex w-full" />
|
|
||||||
<button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">Kirim</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Konten Serupa */}
|
|
||||||
<div className="px-4 lg:px-24">
|
|
||||||
<NewContent group="polda" type={"similar"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DetailVideo;
|
export async function generateMetadata({ params }: any): Promise<Metadata> {
|
||||||
|
const slug = String(params?.slug);
|
||||||
|
const res = await getDetailMetaData(String(slug));
|
||||||
|
const video = res?.data?.data;
|
||||||
|
// console.log("video", video);
|
||||||
|
return {
|
||||||
|
title: video.title,
|
||||||
|
description: video.description,
|
||||||
|
openGraph: {
|
||||||
|
title: video?.title,
|
||||||
|
description: video?.description,
|
||||||
|
videos: [`${video?.smallThumbnailLink}`],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function DetailInfo({ params }: Props) {
|
||||||
|
return <DetailVideo />;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -827,13 +827,13 @@ const FilterPage = () => {
|
||||||
className="hover:scale-110 transition-transform duration-300"
|
className="hover:scale-110 transition-transform duration-300"
|
||||||
>
|
>
|
||||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||||
<div
|
<Link
|
||||||
// href={`/video/detail/${video?.slug}`}
|
href={prefixPath + `/video/detail/${video?.slug}`}
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
router.push(
|
// router.push(
|
||||||
prefixPath + `/video/detail/${video?.slug}`
|
// prefixPath + `/video/detail/${video?.slug}`
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
>
|
>
|
||||||
{/* <img src={video?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" /> */}
|
{/* <img src={video?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" /> */}
|
||||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||||
|
|
@ -862,7 +862,7 @@ const FilterPage = () => {
|
||||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||||
{video?.title}
|
{video?.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import FilterImageComponent from "@/components/landing-page/filter-all/image-fil
|
||||||
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
||||||
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
||||||
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
||||||
|
import FilterIndeksComponent from "@/components/landing-page/filter-all/indeks-filter-card";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function FilterPage() {
|
export default function FilterPage() {
|
||||||
|
|
@ -734,6 +735,13 @@ export default function FilterPage() {
|
||||||
endDateString={endDateString}
|
endDateString={endDateString}
|
||||||
monthYearFilter={monthYearFilter}
|
monthYearFilter={monthYearFilter}
|
||||||
/>
|
/>
|
||||||
|
<FilterIndeksComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
"use server";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const baseUrl = "https://mediahub.polri.go.id/in";
|
||||||
|
|
||||||
|
const response = await getListContent({
|
||||||
|
page: 1,
|
||||||
|
limit: "100",
|
||||||
|
search: "",
|
||||||
|
});
|
||||||
|
const articles = response?.data?.data || [];
|
||||||
|
|
||||||
|
const urls = articles
|
||||||
|
.map((article: any) => {
|
||||||
|
const type =
|
||||||
|
article.fileTypeId == 1
|
||||||
|
? "image"
|
||||||
|
: article.fileTypeId == 2
|
||||||
|
? "video"
|
||||||
|
: article.fileTypeId == 3
|
||||||
|
? "document"
|
||||||
|
: "audio";
|
||||||
|
|
||||||
|
const slug = article.slug
|
||||||
|
? encodeURIComponent(article.slug)
|
||||||
|
: article.id;
|
||||||
|
const lastmod = article.updatedAt
|
||||||
|
? new Date(article.updatedAt).toISOString()
|
||||||
|
: new Date().toISOString();
|
||||||
|
|
||||||
|
return `
|
||||||
|
<url>
|
||||||
|
<loc>${baseUrl}/${type}/detail/${article.id}-${slug}</loc>
|
||||||
|
<lastmod>${lastmod}</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>${baseUrl}</loc>
|
||||||
|
<lastmod>${new Date().toISOString()}</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
${urls}
|
||||||
|
</urlset>`;
|
||||||
|
|
||||||
|
return new NextResponse(sitemap, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/xml",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Sitemap error:", error);
|
||||||
|
return new NextResponse("Sitemap generation failed", { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -763,19 +763,26 @@ export default function FormAudioUpdate() {
|
||||||
const getPlacement = () => {
|
const getPlacement = () => {
|
||||||
const temp = [];
|
const temp = [];
|
||||||
for (let i = 0; i < filePlacements?.length; i++) {
|
for (let i = 0; i < filePlacements?.length; i++) {
|
||||||
if (filePlacements[i]?.length !== 0) {
|
const file = files[i] as any;
|
||||||
const now = filePlacements[i];
|
if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
|
||||||
let nowArr = now?.join(",")?.replaceAll("nasional", "mabes");
|
const now = filePlacements[file.id];
|
||||||
nowArr = nowArr?.replaceAll("wilayah", "polda");
|
const normalizedNow = now.map((item): PlacementType => {
|
||||||
nowArr = nowArr?.replaceAll("semua", "all");
|
const value = String(item);
|
||||||
|
if (value === "nasional") return "mabes";
|
||||||
|
if (value === "wilayah") return "polda";
|
||||||
|
if (value === "semua") return "all";
|
||||||
|
return item as PlacementType;
|
||||||
|
});
|
||||||
|
const uniqueNow = Array.from(new Set(normalizedNow));
|
||||||
|
const nowArr = uniqueNow.join(",");
|
||||||
|
|
||||||
// Dapatkan checked levels untuk file ini
|
// Dapatkan checked levels untuk file ini
|
||||||
const currentFileCheckedLevels = fileCheckedLevels[i]
|
const currentFileCheckedLevels = fileCheckedLevels[file.id]
|
||||||
? Array.from(fileCheckedLevels[i])
|
? Array.from(fileCheckedLevels[file.id])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
mediaFileId: files[i]?.id,
|
mediaFileId: file.id,
|
||||||
placements: nowArr,
|
placements: nowArr,
|
||||||
customLocationPlacements: currentFileCheckedLevels.join(","),
|
customLocationPlacements: currentFileCheckedLevels.join(","),
|
||||||
};
|
};
|
||||||
|
|
@ -860,12 +867,20 @@ export default function FormAudioUpdate() {
|
||||||
}
|
}
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
} else {
|
} else {
|
||||||
|
// Handle mapping dari UI key ke backend value
|
||||||
|
let placementToAdd = placement;
|
||||||
|
if (placement === "nasional") {
|
||||||
|
placementToAdd = "mabes";
|
||||||
|
} else if (placement === "semua") {
|
||||||
|
placementToAdd = "all";
|
||||||
|
}
|
||||||
|
|
||||||
const now = temp[index] || [];
|
const now = temp[index] || [];
|
||||||
if (!now.includes(placement)) {
|
if (!now.includes(placementToAdd)) {
|
||||||
now.push(placement);
|
now.push(placementToAdd);
|
||||||
}
|
}
|
||||||
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
|
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
|
||||||
const requiredItems = ["nasional", "wilayah", "international"];
|
const requiredItems = ["mabes", "wilayah", "international"];
|
||||||
const hasAllRequired = requiredItems.every(item => now.includes(item));
|
const hasAllRequired = requiredItems.every(item => now.includes(item));
|
||||||
if (hasAllRequired && !now.includes("all")) {
|
if (hasAllRequired && !now.includes("all")) {
|
||||||
now.push("all");
|
now.push("all");
|
||||||
|
|
@ -922,14 +937,22 @@ export default function FormAudioUpdate() {
|
||||||
const now = temp[index]?.filter((a) => a !== "satker");
|
const now = temp[index]?.filter((a) => a !== "satker");
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
} else {
|
} else {
|
||||||
const now = temp[index]?.filter((a) => a !== placement);
|
// Handle mapping dari UI key ke backend value
|
||||||
|
let placementToRemove = placement;
|
||||||
|
if (placement === "nasional") {
|
||||||
|
placementToRemove = "mabes";
|
||||||
|
} else if (placement === "semua") {
|
||||||
|
placementToRemove = "all";
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = temp[index]?.filter((a) => a !== placementToRemove);
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hapus "all" jika tidak semua item ter-checklist
|
// Hapus "all" jika tidak semua item ter-checklist
|
||||||
const currentNow = temp[index] || [];
|
const currentNow = temp[index] || [];
|
||||||
if (currentNow.includes("all")) {
|
if (currentNow.includes("all")) {
|
||||||
const requiredItems = ["nasional", "wilayah", "international"];
|
const requiredItems = ["mabes", "wilayah", "international"];
|
||||||
const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
|
const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
|
||||||
if (!hasAllRequired) {
|
if (!hasAllRequired) {
|
||||||
const newData = currentNow.filter((b) => b !== "all");
|
const newData = currentNow.filter((b) => b !== "all");
|
||||||
|
|
@ -1109,9 +1132,11 @@ export default function FormAudioUpdate() {
|
||||||
}
|
}
|
||||||
if (placements.includes("polda")) {
|
if (placements.includes("polda")) {
|
||||||
selection.polda = true;
|
selection.polda = true;
|
||||||
|
selection.wilayah = true; // Auto-check wilayah when polda is present
|
||||||
}
|
}
|
||||||
if (placements.includes("satker")) {
|
if (placements.includes("satker")) {
|
||||||
selection.satker = true;
|
selection.satker = true;
|
||||||
|
selection.wilayah = true; // Auto-check wilayah when satker is present
|
||||||
}
|
}
|
||||||
if (placements.includes("international")) {
|
if (placements.includes("international")) {
|
||||||
selection.international = true;
|
selection.international = true;
|
||||||
|
|
@ -1609,56 +1634,352 @@ export default function FormAudioUpdate() {
|
||||||
</Label>
|
</Label>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{files.map((file: any, index: any) => (
|
{files.map((file: any, index: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.id} // Gunakan ID file sebagai key
|
key={file.id}
|
||||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className="flex items-center border p-2 rounded-md"
|
||||||
>
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
>
|
||||||
<path
|
<div className="flex flex-wrap gap-3 items-center ">
|
||||||
fill="currentColor"
|
<div className="flex-grow">
|
||||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
<div>
|
||||||
/>
|
<svg
|
||||||
</svg>{" "}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<div>
|
width="80"
|
||||||
<div className="text-sm text-card-foreground">
|
height="80"
|
||||||
{file.fileName || file.name}
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||||
|
/>
|
||||||
|
</svg>{" "}
|
||||||
|
<p className="font-medium">{file.fileName}</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href={file.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-500 text-sm"
|
||||||
|
>
|
||||||
|
{t("view-file", {
|
||||||
|
defaultValue: "View File",
|
||||||
|
})}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-light text-muted-foreground">
|
<div className="bg-white rounded-md p-4 border">
|
||||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
{/* Checkbox Tingkat Utama */}
|
||||||
<>
|
<div className="space-y-4">
|
||||||
{(
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
Math.round(file.size / 100) / 10000
|
{[
|
||||||
).toFixed(1)}
|
{ key: "semua", label: "Semua" },
|
||||||
|
{
|
||||||
|
key: "nasional",
|
||||||
|
label: "Nasional",
|
||||||
|
},
|
||||||
|
{ key: "wilayah", label: "Wilayah" },
|
||||||
|
{
|
||||||
|
key: "international",
|
||||||
|
label: "Internasional",
|
||||||
|
},
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileUnitSelections[index]?.[
|
||||||
|
item.key as keyof typeof unitSelection
|
||||||
|
] || false
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleFileUnitChange(
|
||||||
|
index,
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
|
);
|
||||||
|
setupPlacement(
|
||||||
|
index,
|
||||||
|
item.key,
|
||||||
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Detail Wilayah */}
|
||||||
|
{fileUnitSelections[index]?.wilayah && isDetailOfRegionShowed && (
|
||||||
|
<div className="border-t border-gray-200 pt-2">
|
||||||
|
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Detail Wilayah:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
|
{[
|
||||||
|
{ key: "polda", label: "POLDA" },
|
||||||
|
{ key: "satker", label: "SATKER" },
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`${item.key}-${index}`}
|
||||||
|
checked={
|
||||||
|
fileUnitSelections[index]?.[
|
||||||
|
item.key as keyof typeof unitSelection
|
||||||
|
] || false
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleFileUnitChange(
|
||||||
|
index,
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
|
);
|
||||||
|
setupPlacement(
|
||||||
|
index,
|
||||||
|
item.key,
|
||||||
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||||
|
<div className="flex items-center justify-center p-3">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:tune"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
{t("custom", {
|
||||||
|
defaultValue: "Kustom",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||||
|
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||||
|
<DialogTitle className="text-lg font-semibold">
|
||||||
|
Daftar Wilayah POLDA dan SATKER
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||||
|
{listDest.map(
|
||||||
|
(polda: any) => (
|
||||||
|
<div
|
||||||
|
key={polda.id}
|
||||||
|
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||||
|
>
|
||||||
|
{/* Header POLDA */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
index
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
polda.id
|
||||||
|
)
|
||||||
|
) || false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
index,
|
||||||
|
Number(polda.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="font-semibold text-gray-900 text-sm">
|
||||||
|
{polda.name}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
{/* Tombol expand hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleExpand(
|
||||||
|
polda.id
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={
|
||||||
|
expandedPolda[
|
||||||
|
polda.id
|
||||||
|
]
|
||||||
|
? "mdi:chevron-up"
|
||||||
|
: "mdi:chevron-down"
|
||||||
|
}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sub-items hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination &&
|
||||||
|
expandedPolda[
|
||||||
|
polda.id
|
||||||
|
] && (
|
||||||
|
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||||
|
{/* Tombol Pilih Semua untuk sub-items */}
|
||||||
|
<div className="mb-2 flex justify-start">
|
||||||
|
{(() => {
|
||||||
|
const allSubItemsChecked =
|
||||||
|
polda.subDestination?.every(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) =>
|
||||||
|
fileCheckedLevels[
|
||||||
|
index
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs h-6 px-2"
|
||||||
|
onClick={() =>
|
||||||
|
handleSelectAllSubItems(
|
||||||
|
index,
|
||||||
|
polda
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{allSubItemsChecked ? (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:check-indeterminate-small"
|
||||||
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Batal
|
||||||
|
Semua
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{(Math.round(file.size / 100) / 10).toFixed(
|
<Icon
|
||||||
1
|
icon="material-symbols:check-all"
|
||||||
)}
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Pilih
|
||||||
|
Semua
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{" kb"}
|
</Button>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{polda.subDestination.map(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) => (
|
||||||
|
<Label
|
||||||
|
key={
|
||||||
|
sub.id
|
||||||
|
}
|
||||||
|
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
index
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
index,
|
||||||
|
Number(sub.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">
|
||||||
|
{
|
||||||
|
sub.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
</div>
|
||||||
size="icon"
|
)
|
||||||
color="destructive"
|
)}
|
||||||
variant="outline"
|
</div>
|
||||||
className="border-none rounded-full"
|
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
<DialogClose asChild>
|
||||||
>
|
<Button variant="outline">
|
||||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
{t("cancel", {
|
||||||
|
defaultValue: "Batal",
|
||||||
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button>Simpan</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -805,25 +805,31 @@ export default function FormTeksUpdate() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPlacement = () => {
|
const getPlacement = () => {
|
||||||
const temp = [];
|
const temp: Array<{ mediaFileId: number | string; placements: string; customLocationPlacements: string }> = [];
|
||||||
for (let i = 0; i < filePlacements?.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
if (filePlacements[i]?.length !== 0) {
|
const file = files[i] as any;
|
||||||
const now = filePlacements[i];
|
const now = filePlacements[i];
|
||||||
let nowArr = now?.join(",")?.replaceAll("nasional", "mabes");
|
if (file?.id && Array.isArray(now) && now.length > 0) {
|
||||||
nowArr = nowArr?.replaceAll("wilayah", "polda");
|
const normalizedNow = now.map((item): PlacementType => {
|
||||||
nowArr = nowArr?.replaceAll("semua", "all");
|
const value = String(item);
|
||||||
|
if (value === "nasional") return "mabes";
|
||||||
|
if (value === "wilayah") return "polda";
|
||||||
|
if (value === "semua") return "all";
|
||||||
|
return item as PlacementType;
|
||||||
|
});
|
||||||
|
const uniqueNow = Array.from(new Set(normalizedNow));
|
||||||
|
const nowArr = uniqueNow.join(",");
|
||||||
|
|
||||||
// Dapatkan checked levels untuk file ini
|
// Dapatkan checked levels untuk file ini (berbasis index)
|
||||||
const currentFileCheckedLevels = fileCheckedLevels[i]
|
const currentFileCheckedLevels = fileCheckedLevels[i]
|
||||||
? Array.from(fileCheckedLevels[i])
|
? Array.from(fileCheckedLevels[i])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const data = {
|
temp.push({
|
||||||
mediaFileId: files[i]?.id,
|
mediaFileId: file.id,
|
||||||
placements: nowArr,
|
placements: nowArr,
|
||||||
customLocationPlacements: currentFileCheckedLevels.join(","),
|
customLocationPlacements: currentFileCheckedLevels.join(","),
|
||||||
};
|
});
|
||||||
temp.push(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return temp;
|
return temp;
|
||||||
|
|
@ -904,12 +910,20 @@ export default function FormTeksUpdate() {
|
||||||
}
|
}
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
} else {
|
} else {
|
||||||
|
// Handle mapping dari UI key ke backend value
|
||||||
|
let placementToAdd = placement;
|
||||||
|
if (placement === "nasional") {
|
||||||
|
placementToAdd = "mabes";
|
||||||
|
} else if (placement === "semua") {
|
||||||
|
placementToAdd = "all";
|
||||||
|
}
|
||||||
|
|
||||||
const now = temp[index] || [];
|
const now = temp[index] || [];
|
||||||
if (!now.includes(placement)) {
|
if (!now.includes(placementToAdd)) {
|
||||||
now.push(placement);
|
now.push(placementToAdd);
|
||||||
}
|
}
|
||||||
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
|
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
|
||||||
const requiredItems = ["nasional", "wilayah", "international"];
|
const requiredItems = ["mabes", "wilayah", "international"];
|
||||||
const hasAllRequired = requiredItems.every(item => now.includes(item));
|
const hasAllRequired = requiredItems.every(item => now.includes(item));
|
||||||
if (hasAllRequired && !now.includes("all")) {
|
if (hasAllRequired && !now.includes("all")) {
|
||||||
now.push("all");
|
now.push("all");
|
||||||
|
|
@ -966,14 +980,22 @@ export default function FormTeksUpdate() {
|
||||||
const now = temp[index]?.filter((a) => a !== "satker");
|
const now = temp[index]?.filter((a) => a !== "satker");
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
} else {
|
} else {
|
||||||
const now = temp[index]?.filter((a) => a !== placement);
|
// Handle mapping dari UI key ke backend value
|
||||||
|
let placementToRemove = placement;
|
||||||
|
if (placement === "nasional") {
|
||||||
|
placementToRemove = "mabes";
|
||||||
|
} else if (placement === "semua") {
|
||||||
|
placementToRemove = "all";
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = temp[index]?.filter((a) => a !== placementToRemove);
|
||||||
temp[index] = now;
|
temp[index] = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hapus "all" jika tidak semua item ter-checklist
|
// Hapus "all" jika tidak semua item ter-checklist
|
||||||
const currentNow = temp[index] || [];
|
const currentNow = temp[index] || [];
|
||||||
if (currentNow.includes("all")) {
|
if (currentNow.includes("all")) {
|
||||||
const requiredItems = ["nasional", "wilayah", "international"];
|
const requiredItems = ["mabes", "wilayah", "international"];
|
||||||
const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
|
const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
|
||||||
if (!hasAllRequired) {
|
if (!hasAllRequired) {
|
||||||
const newData = currentNow.filter((b) => b !== "all");
|
const newData = currentNow.filter((b) => b !== "all");
|
||||||
|
|
@ -1085,11 +1107,10 @@ export default function FormTeksUpdate() {
|
||||||
}));
|
}));
|
||||||
setFiles(formattedFiles);
|
setFiles(formattedFiles);
|
||||||
|
|
||||||
// Inisialisasi filePlacements dari detail
|
// Inisialisasi filePlacements dari detail (biarkan format backend, normalisasi dilakukan saat submit)
|
||||||
const initialFilePlacements: string[][] = details.files.map((file: any) => {
|
const initialFilePlacements: string[][] = details.files.map((file: any) => {
|
||||||
if (file.placements) {
|
if (file.placements) {
|
||||||
const placements = file.placements.split(",").map((p: string) => p.trim());
|
return file.placements.split(",").map((p: string) => p.trim());
|
||||||
return placements;
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
@ -1123,8 +1144,7 @@ export default function FormTeksUpdate() {
|
||||||
|
|
||||||
if (file.placements) {
|
if (file.placements) {
|
||||||
const placements = file.placements.split(",").map((p: string) => p.trim());
|
const placements = file.placements.split(",").map((p: string) => p.trim());
|
||||||
|
|
||||||
// Map dari format backend ke checkbox
|
|
||||||
if (placements.includes("all")) {
|
if (placements.includes("all")) {
|
||||||
selection.semua = true;
|
selection.semua = true;
|
||||||
selection.nasional = true;
|
selection.nasional = true;
|
||||||
|
|
@ -1133,23 +1153,21 @@ export default function FormTeksUpdate() {
|
||||||
selection.polda = true;
|
selection.polda = true;
|
||||||
selection.satker = true;
|
selection.satker = true;
|
||||||
} else {
|
} else {
|
||||||
if (placements.includes("mabes")) {
|
if (placements.includes("mabes")) selection.nasional = true;
|
||||||
selection.nasional = true;
|
if (placements.includes("international")) selection.international = true;
|
||||||
}
|
if (placements.includes("polda")) selection.polda = true;
|
||||||
if (placements.includes("wilayah")) {
|
if (placements.includes("satker")) selection.satker = true;
|
||||||
|
// Wilayah aktif jika ada "wilayah" ATAU ada polda/satker
|
||||||
|
if (
|
||||||
|
placements.includes("wilayah") ||
|
||||||
|
placements.includes("polda") ||
|
||||||
|
placements.includes("satker")
|
||||||
|
) {
|
||||||
selection.wilayah = true;
|
selection.wilayah = true;
|
||||||
}
|
}
|
||||||
if (placements.includes("polda")) {
|
|
||||||
selection.polda = true;
|
|
||||||
}
|
|
||||||
if (placements.includes("satker")) {
|
|
||||||
selection.satker = true;
|
|
||||||
}
|
|
||||||
if (placements.includes("international")) {
|
|
||||||
selection.international = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return selection;
|
return selection;
|
||||||
});
|
});
|
||||||
setFileUnitSelections(initialFileUnitSelections);
|
setFileUnitSelections(initialFileUnitSelections);
|
||||||
|
|
@ -1241,6 +1259,16 @@ export default function FormTeksUpdate() {
|
||||||
setIsStartUpload(true);
|
setIsStartUpload(true);
|
||||||
setProgressList(progressInfoArr);
|
setProgressList(progressInfoArr);
|
||||||
|
|
||||||
|
// Update file placements terlebih dahulu (sebelum upload)
|
||||||
|
const placementData = getPlacement();
|
||||||
|
if (placementData.length > 0) {
|
||||||
|
const responseFilePlacements = await updateFilePlacements(placementData);
|
||||||
|
if (responseFilePlacements?.error) {
|
||||||
|
error(responseFilePlacements?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
// showProgress();
|
// showProgress();
|
||||||
files.map(async (item: any, index: number) => {
|
files.map(async (item: any, index: number) => {
|
||||||
|
|
@ -1252,12 +1280,6 @@ export default function FormTeksUpdate() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update file placements
|
|
||||||
const placementData = getPlacement();
|
|
||||||
if (placementData.length > 0) {
|
|
||||||
await updateFilePlacements(placementData);
|
|
||||||
}
|
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
text: "Data berhasil disimpan.",
|
text: "Data berhasil disimpan.",
|
||||||
|
|
@ -1595,13 +1617,13 @@ export default function FormTeksUpdate() {
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4">
|
||||||
<Label className="text-lg font-semibold">
|
<Label className="text-md font-semibold">
|
||||||
{" "}
|
{" "}
|
||||||
{t("file-media", { defaultValue: "File Media" })}
|
{t("file-media", { defaultValue: "File Media" })}
|
||||||
</Label>
|
</Label>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{files.map((file: any) => (
|
{files.map((file: any, index: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.id}
|
key={file.id}
|
||||||
className="flex items-center border p-2 rounded-md"
|
className="flex items-center border p-2 rounded-md"
|
||||||
|
|
@ -1625,193 +1647,314 @@ export default function FormTeksUpdate() {
|
||||||
})}
|
})}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="bg-white rounded-md p-4 border">
|
||||||
<Label className="flex items-center space-x-2">
|
{/* Checkbox Tingkat Utama */}
|
||||||
<input
|
<div className="space-y-4">
|
||||||
type="checkbox"
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.semua || false}
|
{[
|
||||||
onChange={(e) =>
|
{ key: "semua", label: "Semua" },
|
||||||
handleFileUnitChange(
|
{
|
||||||
files.indexOf(file),
|
key: "nasional",
|
||||||
"semua",
|
label: "Nasional",
|
||||||
e.target.checked
|
},
|
||||||
)
|
{ key: "wilayah", label: "Wilayah" },
|
||||||
}
|
{
|
||||||
className="form-checkbox"
|
key: "international",
|
||||||
/>
|
label: "Internasional",
|
||||||
<span>
|
},
|
||||||
{t("all", { defaultValue: "All" })}
|
].map((item, idx) => (
|
||||||
</span>
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
// id={`${item.key}-${index}`}
|
||||||
|
checked={
|
||||||
|
fileUnitSelections[files.indexOf(file)]?.[
|
||||||
|
item.key as keyof typeof unitSelection
|
||||||
|
] || false
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleFileUnitChange(
|
||||||
|
files.indexOf(file),
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
|
);
|
||||||
|
setupPlacement(
|
||||||
|
files.indexOf(file),
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
))}
|
||||||
<Label className="flex items-center space-x-2">
|
</div>
|
||||||
<input
|
|
||||||
type="checkbox"
|
{/* Detail Wilayah */}
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.nasional || false}
|
{fileUnitSelections[files.indexOf(file)]?.wilayah && isDetailOfRegionShowed && (
|
||||||
onChange={(e) =>
|
<div className="border-t border-gray-200 pt-2">
|
||||||
handleFileUnitChange(
|
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||||
files.indexOf(file),
|
Detail Wilayah:
|
||||||
"nasional",
|
</p>
|
||||||
e.target.checked
|
|
||||||
)
|
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||||
}
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
className="form-checkbox"
|
{[
|
||||||
/>
|
{ key: "polda", label: "POLDA" },
|
||||||
<span>Nasional</span>
|
{ key: "satker", label: "SATKER" },
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`${item.key}-${index}`}
|
||||||
|
checked={
|
||||||
|
fileUnitSelections[files.indexOf(file)]?.[
|
||||||
|
item.key as keyof typeof unitSelection
|
||||||
|
] || false
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleFileUnitChange(
|
||||||
|
files.indexOf(file),
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
|
);
|
||||||
|
setupPlacement(
|
||||||
|
files.indexOf(file),
|
||||||
|
item.key as keyof typeof unitSelection,
|
||||||
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
))}
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<input
|
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||||
type="checkbox"
|
<div className="flex items-center justify-center p-3">
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.wilayah || false}
|
<Dialog>
|
||||||
onChange={(e) =>
|
<DialogTrigger asChild>
|
||||||
handleFileUnitChange(
|
<Button
|
||||||
files.indexOf(file),
|
variant="outline"
|
||||||
"wilayah",
|
size="sm"
|
||||||
e.target.checked
|
className="gap-2"
|
||||||
)
|
>
|
||||||
}
|
<Icon
|
||||||
className="form-checkbox"
|
icon="material-symbols:tune"
|
||||||
/>
|
width={16}
|
||||||
<span>Wilayah</span>
|
height={16}
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.international || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFileUnitChange(
|
|
||||||
files.indexOf(file),
|
|
||||||
"international",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<span>Internasional</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.polda || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFileUnitChange(
|
|
||||||
files.indexOf(file),
|
|
||||||
"polda",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<span>POLDA</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fileUnitSelections[files.indexOf(file)]?.satker || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFileUnitChange(
|
|
||||||
files.indexOf(file),
|
|
||||||
"satker",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<span>SATKER</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
Detail Wilayah
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-2xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Daftar Wilayah</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="max-h-96 overflow-y-auto">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex justify-center items-center h-32">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{listDest.map((polda: any) => (
|
|
||||||
<div key={polda.id} className="space-y-1">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fileCheckedLevels[files.indexOf(file)]?.has(Number(polda.id)) || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFileCheckboxChangePlacement(
|
|
||||||
files.indexOf(file),
|
|
||||||
Number(polda.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
/>
|
||||||
<span className="font-medium">
|
{t("custom", {
|
||||||
{polda.name}
|
defaultValue: "Kustom",
|
||||||
</span>
|
})}
|
||||||
{polda.name === "SATKER POLRI" && polda.subDestination && (
|
</Button>
|
||||||
<Button
|
</DialogTrigger>
|
||||||
variant="outline"
|
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||||
size="sm"
|
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||||
onClick={() => toggleExpand(Number(polda.id))}
|
<DialogTitle className="text-lg font-semibold">
|
||||||
>
|
Daftar Wilayah POLDA dan SATKER
|
||||||
{expandedPolda[Number(polda.id)] ? "Tutup" : "Buka"}
|
</DialogTitle>
|
||||||
</Button>
|
</DialogHeader>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||||
|
{listDest.map(
|
||||||
|
(polda: any) => (
|
||||||
|
<div
|
||||||
|
key={polda.id}
|
||||||
|
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||||
|
>
|
||||||
|
{/* Header POLDA */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
files.indexOf(file)
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
polda.id
|
||||||
|
)
|
||||||
|
) || false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
files.indexOf(file),
|
||||||
|
Number(polda.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="font-semibold text-gray-900 text-sm">
|
||||||
|
{polda.name}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
{/* Tombol expand hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleExpand(
|
||||||
|
Number(polda.id)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={
|
||||||
|
expandedPolda[Number(polda.id)]
|
||||||
|
? "mdi:chevron-up"
|
||||||
|
: "mdi:chevron-down"
|
||||||
|
}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sub-items hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination &&
|
||||||
|
expandedPolda[Number(polda.id)] && (
|
||||||
|
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||||
|
{/* Tombol Pilih Semua untuk sub-items */}
|
||||||
|
<div className="mb-2 flex justify-start">
|
||||||
|
{(() => {
|
||||||
|
const allSubItemsChecked =
|
||||||
|
polda.subDestination?.every(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) =>
|
||||||
|
fileCheckedLevels[
|
||||||
|
files.indexOf(file)
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs h-6 px-2"
|
||||||
|
onClick={() =>
|
||||||
|
handleSelectAllSubItems(
|
||||||
|
files.indexOf(file),
|
||||||
|
polda
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{allSubItemsChecked ? (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:check-indeterminate-small"
|
||||||
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Batal
|
||||||
|
Semua
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:check-all"
|
||||||
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Pilih
|
||||||
|
Semua
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{polda.subDestination.map(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) => (
|
||||||
|
<Label
|
||||||
|
key={
|
||||||
|
sub.id
|
||||||
|
}
|
||||||
|
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
files.indexOf(file)
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
files.indexOf(file),
|
||||||
|
Number(sub.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">
|
||||||
|
{
|
||||||
|
sub.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{polda.name === "SATKER POLRI" && polda.subDestination && expandedPolda[Number(polda.id)] && (
|
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||||
<div className="ml-6 space-y-1">
|
<DialogClose asChild>
|
||||||
<div className="flex items-center space-x-2">
|
<Button variant="outline">
|
||||||
<Button
|
{t("cancel", {
|
||||||
variant="outline"
|
defaultValue: "Batal",
|
||||||
size="sm"
|
})}
|
||||||
onClick={() => handleSelectAllSubItems(files.indexOf(file), polda)}
|
</Button>
|
||||||
>
|
</DialogClose>
|
||||||
{polda.subDestination.every((sub: any) =>
|
<DialogClose asChild>
|
||||||
fileCheckedLevels[files.indexOf(file)]?.has(Number(sub.id))
|
<Button>Simpan</Button>
|
||||||
) ? "Batal Pilih Semua" : "Pilih Semua"}
|
</DialogClose>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
{polda.subDestination.map((sub: any) => (
|
</Dialog>
|
||||||
<div key={sub.id} className="flex items-center space-x-2 ml-4">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fileCheckedLevels[files.indexOf(file)]?.has(Number(sub.id)) || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFileCheckboxChangePlacement(
|
|
||||||
files.indexOf(file),
|
|
||||||
Number(sub.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<span className="text-sm">{sub.name}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
)}
|
||||||
</Dialog>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
|
||||||
import { register } from "module";
|
import { register } from "module";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
@ -94,6 +94,7 @@ type Detail = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
|
id: string;
|
||||||
preview: string;
|
preview: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,6 +148,9 @@ export default function FormVideoUpdate() {
|
||||||
type VideoSchema = z.infer<typeof videoSchema>;
|
type VideoSchema = z.infer<typeof videoSchema>;
|
||||||
let progressInfo: any = [];
|
let progressInfo: any = [];
|
||||||
let counterUpdateProgress = 0;
|
let counterUpdateProgress = 0;
|
||||||
|
|
||||||
|
const isDetailOfRegionShowed = false;
|
||||||
|
|
||||||
const [progressList, setProgressList] = useState<any>([]);
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
let uploadPersen = 0;
|
let uploadPersen = 0;
|
||||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||||
|
|
@ -765,19 +769,36 @@ export default function FormVideoUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPlacement = (fileId?: string): string => {
|
const getPlacement = () => {
|
||||||
if (fileId && filePlacements[fileId]) {
|
const temp = [];
|
||||||
const placements = filePlacements[fileId];
|
for (let i = 0; i < files.length; i++) {
|
||||||
let nowArr = placements.join(",");
|
const file = files[i] as any;
|
||||||
nowArr = nowArr?.replaceAll("all", "all");
|
if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
|
||||||
nowArr = nowArr?.replaceAll("mabes", "mabes");
|
const now = filePlacements[file.id];
|
||||||
nowArr = nowArr?.replaceAll("wilayah", "polda");
|
const normalizedNow = now.map((item): PlacementType => {
|
||||||
nowArr = nowArr?.replaceAll("polda", "polda");
|
const value = String(item);
|
||||||
nowArr = nowArr?.replaceAll("satker", "satker");
|
if (value === "nasional") return "mabes";
|
||||||
nowArr = nowArr?.replaceAll("international", "international");
|
if (value === "wilayah") return "polda";
|
||||||
return nowArr;
|
if (value === "semua") return "all";
|
||||||
|
return item as PlacementType;
|
||||||
|
});
|
||||||
|
const uniqueNow = Array.from(new Set(normalizedNow));
|
||||||
|
const nowArr = uniqueNow.join(",");
|
||||||
|
|
||||||
|
// Dapatkan checked levels untuk file ini
|
||||||
|
const currentFileCheckedLevels = fileCheckedLevels[file.id]
|
||||||
|
? Array.from(fileCheckedLevels[file.id])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
mediaFileId: file.id,
|
||||||
|
placements: nowArr,
|
||||||
|
customLocationPlacements: currentFileCheckedLevels.join(","),
|
||||||
|
};
|
||||||
|
temp.push(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "";
|
return temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupPlacement = (fileId: string, placement: string, isChecked: boolean) => {
|
const setupPlacement = (fileId: string, placement: string, isChecked: boolean) => {
|
||||||
|
|
@ -927,12 +948,10 @@ export default function FormVideoUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update file placements
|
// Update file placements
|
||||||
if (files.length > 0) {
|
const responseFilePlacements = await updateFilePlacements(getPlacement());
|
||||||
files.forEach((file: any) => {
|
if (responseFilePlacements?.error) {
|
||||||
if (file.id) {
|
error(responseFilePlacements?.message);
|
||||||
updateFilePlacements(getPlacement(file.id));
|
return false;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFiles.length > 0) {
|
if (selectedFiles.length > 0) {
|
||||||
|
|
@ -1318,13 +1337,13 @@ export default function FormVideoUpdate() {
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4">
|
||||||
<Label className="text-lg font-semibold">
|
<Label className="text-md font-semibold">
|
||||||
{" "}
|
{" "}
|
||||||
{t("file-media", { defaultValue: "File Media" })}
|
{t("file-media", { defaultValue: "File Media" })}
|
||||||
</Label>
|
</Label>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{files.map((file: any) => (
|
{files.map((file: any, index: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.id}
|
key={file.id}
|
||||||
className="flex items-center border p-2 rounded-md"
|
className="flex items-center border p-2 rounded-md"
|
||||||
|
|
@ -1348,136 +1367,319 @@ export default function FormVideoUpdate() {
|
||||||
})}
|
})}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3 items-center">
|
<div className="bg-white rounded-md p-4 border">
|
||||||
<div>
|
{/* Checkbox Tingkat Utama */}
|
||||||
<Label className="flex items-center space-x-2">
|
<div className="space-y-4">
|
||||||
<Checkbox
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
checked={fileUnitSelections[file.id]?.semua || false}
|
{[
|
||||||
onCheckedChange={(checked) =>
|
{ key: "semua", label: "Semua" },
|
||||||
handleFileUnitChange(file.id, "semua", checked as boolean)
|
{
|
||||||
}
|
key: "nasional",
|
||||||
/>
|
label: "Nasional",
|
||||||
<span>
|
},
|
||||||
{t("all", { defaultValue: "All" })}
|
{ key: "wilayah", label: "Wilayah" },
|
||||||
</span>
|
{
|
||||||
</Label>
|
key: "international",
|
||||||
</div>
|
label: "Internasional",
|
||||||
<div>
|
},
|
||||||
<Label className="flex items-center space-x-2">
|
].map((item, idx) => (
|
||||||
<Checkbox
|
<div
|
||||||
checked={fileUnitSelections[file.id]?.nasional || false}
|
key={item.key}
|
||||||
onCheckedChange={(checked) =>
|
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
handleFileUnitChange(file.id, "nasional", checked as boolean)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>Nasional</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={fileUnitSelections[file.id]?.wilayah || false}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleFileUnitChange(file.id, "wilayah", checked as boolean)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>Wilayah</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={fileUnitSelections[file.id]?.international || false}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleFileUnitChange(file.id, "international", checked as boolean)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>Internasional</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setTempFile(file)}
|
|
||||||
>
|
>
|
||||||
Detail Wilayah
|
<Checkbox
|
||||||
</Button>
|
// id={`${item.key}-${index}`}
|
||||||
</DialogTrigger>
|
checked={
|
||||||
<DialogContent className="max-w-2xl">
|
fileUnitSelections[file.id]?.[
|
||||||
<DialogHeader>
|
item.key as keyof typeof unitSelection
|
||||||
<DialogTitle>Daftar Wilayah POLDA dan SATKER</DialogTitle>
|
] || false
|
||||||
</DialogHeader>
|
}
|
||||||
<div className="max-h-96 overflow-y-auto">
|
onCheckedChange={(value) => {
|
||||||
{listDest?.filter((item) => item.levelNumber === 2)
|
handleFileUnitChange(
|
||||||
.map((poldaItem) => (
|
file.id,
|
||||||
<div key={poldaItem.id} className="mb-4">
|
item.key,
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
value as boolean
|
||||||
<Checkbox
|
);
|
||||||
checked={fileCheckedLevels[file.id]?.has(poldaItem.id) || false}
|
setupPlacement(
|
||||||
onCheckedChange={(checked) =>
|
file.id,
|
||||||
handleFileCheckboxChangePlacement(file.id, poldaItem, checked as boolean)
|
item.key,
|
||||||
}
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Detail Wilayah */}
|
||||||
|
{fileUnitSelections[file.id]?.wilayah && isDetailOfRegionShowed && (
|
||||||
|
<div className="border-t border-gray-200 pt-2">
|
||||||
|
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Detail Wilayah:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
|
{[
|
||||||
|
{ key: "polda", label: "POLDA" },
|
||||||
|
{ key: "satker", label: "SATKER" },
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`${item.key}-${index}`}
|
||||||
|
checked={
|
||||||
|
fileUnitSelections[file.id]?.[
|
||||||
|
item.key as keyof typeof unitSelection
|
||||||
|
] || false
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleFileUnitChange(
|
||||||
|
file.id,
|
||||||
|
item.key,
|
||||||
|
value as boolean
|
||||||
|
);
|
||||||
|
setupPlacement(
|
||||||
|
file.id,
|
||||||
|
item.key,
|
||||||
|
Boolean(value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${item.key}-${index}`}
|
||||||
|
className="text-sm font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||||
|
<div className="flex items-center justify-center p-3">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:tune"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
/>
|
/>
|
||||||
<Label className="font-medium">
|
{t("custom", {
|
||||||
{poldaItem.name}
|
defaultValue: "Kustom",
|
||||||
</Label>
|
})}
|
||||||
{poldaItem.name === "SATKER POLRI" && poldaItem.subDestination.length > 0 && (
|
</Button>
|
||||||
<Button
|
</DialogTrigger>
|
||||||
type="button"
|
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||||
variant="ghost"
|
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||||
size="sm"
|
<DialogTitle className="text-lg font-semibold">
|
||||||
onClick={() => toggleExpand(poldaItem.id)}
|
Daftar Wilayah POLDA dan SATKER
|
||||||
>
|
</DialogTitle>
|
||||||
{expandedPolda.has(poldaItem.id) ? "Tutup" : "Buka"}
|
</DialogHeader>
|
||||||
</Button>
|
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||||
|
{listDest.map(
|
||||||
|
(polda: any) => (
|
||||||
|
<div
|
||||||
|
key={polda.id}
|
||||||
|
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||||
|
>
|
||||||
|
{/* Header POLDA */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
file.id
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
polda.id
|
||||||
|
)
|
||||||
|
) || false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
file.id,
|
||||||
|
polda,
|
||||||
|
!fileCheckedLevels[file.id]?.has(Number(polda.id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="font-semibold text-gray-900 text-sm">
|
||||||
|
{polda.name}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
{/* Tombol expand hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleExpand(
|
||||||
|
polda.id
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={
|
||||||
|
expandedPolda.has(
|
||||||
|
polda.id
|
||||||
|
)
|
||||||
|
? "mdi:chevron-up"
|
||||||
|
: "mdi:chevron-down"
|
||||||
|
}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sub-items hanya untuk SATKER POLRI */}
|
||||||
|
{polda.name === "SATKER POLRI" && polda.subDestination &&
|
||||||
|
expandedPolda.has(
|
||||||
|
polda.id
|
||||||
|
) && (
|
||||||
|
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||||
|
{/* Tombol Pilih Semua untuk sub-items */}
|
||||||
|
<div className="mb-2 flex justify-start">
|
||||||
|
{(() => {
|
||||||
|
const allSubItemsChecked =
|
||||||
|
polda.subDestination?.every(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) =>
|
||||||
|
fileCheckedLevels[
|
||||||
|
file.id
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs h-6 px-2"
|
||||||
|
onClick={() =>
|
||||||
|
handleSelectAllSubItems(
|
||||||
|
file.id,
|
||||||
|
polda
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{allSubItemsChecked ? (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:check-indeterminate-small"
|
||||||
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Batal
|
||||||
|
Semua
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:check-all"
|
||||||
|
width={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
height={
|
||||||
|
12
|
||||||
|
}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
Pilih
|
||||||
|
Semua
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{polda.subDestination.map(
|
||||||
|
(
|
||||||
|
sub: any
|
||||||
|
) => (
|
||||||
|
<Label
|
||||||
|
key={
|
||||||
|
sub.id
|
||||||
|
}
|
||||||
|
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
fileCheckedLevels[
|
||||||
|
file.id
|
||||||
|
]?.has(
|
||||||
|
Number(
|
||||||
|
sub.id
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleFileCheckboxChangePlacement(
|
||||||
|
file.id,
|
||||||
|
polda,
|
||||||
|
!fileCheckedLevels[file.id]?.has(Number(sub.id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">
|
||||||
|
{
|
||||||
|
sub.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{poldaItem.name === "SATKER POLRI" && expandedPolda.has(poldaItem.id) && (
|
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||||
<div className="ml-6 space-y-2">
|
<DialogClose asChild>
|
||||||
<div className="flex items-center space-x-2">
|
<Button variant="outline">
|
||||||
<Button
|
{t("cancel", {
|
||||||
type="button"
|
defaultValue: "Batal",
|
||||||
variant="outline"
|
})}
|
||||||
size="sm"
|
</Button>
|
||||||
onClick={() => handleSelectAllSubItems(file.id, poldaItem)}
|
</DialogClose>
|
||||||
>
|
<DialogClose asChild>
|
||||||
Pilih Semua
|
<Button>Simpan</Button>
|
||||||
</Button>
|
</DialogClose>
|
||||||
</div>
|
</div>
|
||||||
{poldaItem.subDestination.map((satkerItem) => (
|
</DialogContent>
|
||||||
<div key={satkerItem.id} className="flex items-center space-x-2 ml-4">
|
</Dialog>
|
||||||
<Checkbox
|
</div>
|
||||||
checked={fileCheckedLevels[file.id]?.has(satkerItem.id) || false}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
setFileCheckedLevels(prev => {
|
|
||||||
const currentFileLevels = prev[file.id] || new Set<number>();
|
|
||||||
const newFileLevels = new Set(currentFileLevels);
|
|
||||||
if (checked) {
|
|
||||||
newFileLevels.add(satkerItem.id);
|
|
||||||
} else {
|
|
||||||
newFileLevels.delete(satkerItem.id);
|
|
||||||
}
|
|
||||||
return { ...prev, [file.id]: newFileLevels };
|
|
||||||
});
|
|
||||||
updateMainCheckboxFromModal(file.id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label className="text-sm">
|
|
||||||
{satkerItem.name}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</div>
|
||||||
</Dialog>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -190,10 +190,11 @@ export default function FilterImageComponent(props: {
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{newContent?.map((image: any) => (
|
{newContent?.map((image: any) => (
|
||||||
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||||
<div
|
<Link
|
||||||
onClick={() =>
|
href={prefixPath + `/image/detail/${image?.slug}`}
|
||||||
router.push(prefixPath + `/image/detail/${image?.slug}`)
|
// onClick={() =>
|
||||||
}
|
// router.push(prefixPath + `/image/detail/${image?.slug}`)
|
||||||
|
// }
|
||||||
className="cursor-pointer relative group overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300"
|
className="cursor-pointer relative group overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300"
|
||||||
>
|
>
|
||||||
{/* Image with motion effect */}
|
{/* Image with motion effect */}
|
||||||
|
|
@ -260,7 +261,7 @@ export default function FilterImageComponent(props: {
|
||||||
</p>
|
</p>
|
||||||
*/}
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
|
|
|
||||||
|
|
@ -578,7 +578,7 @@ const DetailImage = (data: any) => {
|
||||||
height={1440}
|
height={1440}
|
||||||
src={detailDataImage?.files[selectedImage]?.url}
|
src={detailDataImage?.files[selectedImage]?.url}
|
||||||
alt="Main"
|
alt="Main"
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
className="rounded-lg h-[300px] w-screen lg:h-[570px] lg:w-full object-contain"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="absolute top-4 right-4"></div>
|
<div className="absolute top-4 right-4"></div>
|
||||||
|
|
|
||||||
|
|
@ -472,9 +472,9 @@ const DetailVideo = () => {
|
||||||
<VideoPlayer url={detailDataVideo?.files[selectedVideo]?.url} />
|
<VideoPlayer url={detailDataVideo?.files[selectedVideo]?.url} />
|
||||||
<div className="absolute top-4 left-4"></div>
|
<div className="absolute top-4 left-4"></div>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="relative max-h-screen overflow-hidden">
|
<div className="relative max-h-[600px] overflow-hidden">
|
||||||
<div className="w-full max-h-screen aspect-video">
|
<div className="w-full max-h-[600px] aspect-video">
|
||||||
<div className="w-full h-full object-contain">
|
<div className="w-full h-[600px] object-contain">
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
url={detailDataVideo?.files[selectedVideo]?.url}
|
url={detailDataVideo?.files[selectedVideo]?.url}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
122
lib/menus.ts
122
lib/menus.ts
|
|
@ -3214,9 +3214,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/supervisor/ticketing/4",
|
href: "/supervisor/ticketing/3",
|
||||||
label: "Instagram",
|
label: "Instagram",
|
||||||
active: pathname.includes("/ticketing/4"),
|
active: pathname.includes("/ticketing/3"),
|
||||||
icon: "ri:chat-private-line",
|
icon: "ri:chat-private-line",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -3230,73 +3230,73 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
href: "/supervisor/ticketing/5",
|
href: "/supervisor/ticketing/5",
|
||||||
label: "Youtube",
|
label: "Youtube",
|
||||||
active: pathname.includes("/ticketing/youtube"),
|
active: pathname.includes("/ticketing/5"),
|
||||||
icon: "ri:share-forward-2-fill",
|
icon: "ri:share-forward-2-fill",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Tiktok",
|
||||||
|
// active: pathname.includes("/ticketing/tiktok"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Kolom Komentar",
|
||||||
|
// active: pathname.includes("/ticketing/comment"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Hubungi Kami",
|
||||||
|
// active: pathname.includes("/ticketing/contact-us"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Play Store",
|
||||||
|
// active: pathname.includes("/ticketing/play-store"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "App Store",
|
||||||
|
// active: pathname.includes("/ticketing/app-store"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Web Humas",
|
||||||
|
// active: pathname.includes("/ticketing/web-humas"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "e-PPID",
|
||||||
|
// active: pathname.includes("/ticketing/e-ppid"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
href: "/",
|
href: "/supervisor/ticketing/6",
|
||||||
label: "Tiktok",
|
|
||||||
active: pathname.includes("/ticketing/tiktok"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Kolom Komentar",
|
|
||||||
active: pathname.includes("/ticketing/comment"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Hubungi Kami",
|
|
||||||
active: pathname.includes("/ticketing/contact-us"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Play Store",
|
|
||||||
active: pathname.includes("/ticketing/play-store"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "App Store",
|
|
||||||
active: pathname.includes("/ticketing/app-store"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Web Humas",
|
|
||||||
active: pathname.includes("/ticketing/web-humas"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "e-PPID",
|
|
||||||
active: pathname.includes("/ticketing/e-ppid"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Emergency Issues",
|
label: "Emergency Issues",
|
||||||
active: pathname.includes("/ticketing/emergency-issues"),
|
active: pathname.includes("/ticketing/6"),
|
||||||
icon: "ri:share-forward-2-fill",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "/",
|
|
||||||
label: "Campaignpool",
|
|
||||||
active: pathname.includes("/ticketing/campaignpool"),
|
|
||||||
icon: "ri:share-forward-2-fill",
|
icon: "ri:share-forward-2-fill",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// href: "/",
|
||||||
|
// label: "Campaignpool",
|
||||||
|
// active: pathname.includes("/ticketing/campaignpool"),
|
||||||
|
// icon: "ri:share-forward-2-fill",
|
||||||
|
// children: [],
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,34 @@ type VideoPlayerProps = {
|
||||||
|
|
||||||
const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
||||||
return (
|
return (
|
||||||
|
// <ReactPlayer
|
||||||
|
// className="react-player"
|
||||||
|
// width="100%"
|
||||||
|
// height="100%"
|
||||||
|
// playing
|
||||||
|
// pip
|
||||||
|
// controls
|
||||||
|
// config={{
|
||||||
|
// file: {
|
||||||
|
// attributes: {
|
||||||
|
// controlsList: "nodownload",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// url={url}
|
||||||
|
// />
|
||||||
<ReactPlayer
|
<ReactPlayer
|
||||||
className="react-player"
|
className="react-player"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
playing
|
|
||||||
pip
|
|
||||||
controls
|
controls
|
||||||
|
playing
|
||||||
|
muted
|
||||||
|
pip={false} //
|
||||||
config={{
|
config={{
|
||||||
file: {
|
file: {
|
||||||
attributes: {
|
attributes: {
|
||||||
controlsList: "nodownload",
|
controlsList: "nodownload",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
@ -26,4 +43,4 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VideoPlayer;
|
export default VideoPlayer;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue