This commit is contained in:
hanif salafi 2025-07-31 12:46:10 +07:00
commit f854cb7385
13 changed files with 163 additions and 102 deletions

View File

@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { close, error, loading, success } from "@/config/swal";
import { deleteMediaBlastCampaign } from "@/service/broadcast/broadcast";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -62,6 +66,34 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const MySwal = withReactContent(Swal);
const handleDelete = (id: any) => {
MySwal.fire({
title: "Apakah anda ingin menghapus data?",
showCancelButton: true,
confirmButtonColor: "#dc3545",
confirmButtonText: "Iya",
cancelButtonText: "Tidak",
}).then((result: any) => {
if (result.isConfirmed) {
doDeleteAccount(id);
}
});
};
async function doDeleteAccount(id: any) {
loading();
const response = await deleteMediaBlastCampaign(id);
close();
if (response.error) {
error(response.message);
return false;
}
// success();
}
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>

View File

@ -423,7 +423,7 @@ const ContentListBanner = () => {
checked={selectedItems.length === data.length} checked={selectedItems.length === data.length}
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
<span>Pilih Semua</span> <span className="text-black dark:text-white">Pilih Semua</span>
</div> </div>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<Button color="primary" onClick={() => handleBanner(selectedItems)}> <Button color="primary" onClick={() => handleBanner(selectedItems)}>

View File

@ -13,7 +13,7 @@ export default function AdminBanner() {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3"> <div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
<div className="flex justify-between"> <div className="flex justify-between">
{selectedTab === "content" ? "List Media" : " List Banner"} {selectedTab === "content" ? "List Media" : " List Banner"}

View File

@ -162,11 +162,11 @@ export default function EditCategoryModal(props: {
}, [id]); }, [id]);
function removeAndReturn(inputString: string, toRemove: number[]) { function removeAndReturn(inputString: string, toRemove: number[]) {
const numbers = inputString.split(",").map(Number); const numbers = inputString?.split(",").map(Number);
const filteredNumbers = numbers.filter((num) => !toRemove.includes(num)); const filteredNumbers = numbers?.filter((num) => !toRemove?.includes(num));
return filteredNumbers.map(String); return filteredNumbers?.map(String);
} }
function filterString(inputString: string, type: string) { function filterString(inputString: string, type: string) {

View File

@ -8,7 +8,7 @@ const ImageCreatePage = async () => {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
<div className="space-y-4"> <div className="space-y-4 ">
<FormImage /> <FormImage />
</div> </div>
</div> </div>

View File

@ -8,6 +8,7 @@ import {
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -21,9 +22,33 @@ import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
const useTableColumns = () => { const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook const t = useTranslations("Table");
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(val) => table.toggleAllPageRowsSelected(!!val)}
aria-label="Pilih semua pada halaman ini"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(val) => row.toggleSelected(!!val)}
aria-label="Pilih baris"
/>
),
enableSorting: false,
enableHiding: false,
},
{ {
accessorKey: "no", accessorKey: "no",
header: t("no", { defaultValue: "No" }), header: t("no", { defaultValue: "No" }),
@ -56,7 +81,6 @@ const useTableColumns = () => {
<span className="whitespace-nowrap">{row.getValue("contentTag")}</span> <span className="whitespace-nowrap">{row.getValue("contentTag")}</span>
), ),
}, },
{ {
accessorKey: "contentType", accessorKey: "contentType",
header: t("type-content", { defaultValue: "Type Content" }), header: t("type-content", { defaultValue: "Type Content" }),
@ -73,7 +97,6 @@ const useTableColumns = () => {
</span> </span>
), ),
}, },
{ {
accessorKey: "isPublish", accessorKey: "isPublish",
header: "Status", header: "Status",
@ -95,7 +118,6 @@ const useTableColumns = () => {
); );
}, },
}, },
{ {
accessorKey: "contentCreatedDate", accessorKey: "contentCreatedDate",
header: t("upload-date", { defaultValue: "Upload Date" }), header: t("upload-date", { defaultValue: "Upload Date" }),
@ -104,7 +126,6 @@ const useTableColumns = () => {
| string | string
| number | number
| undefined; | undefined;
const formattedDate = const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime()) createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss") ? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
@ -118,8 +139,7 @@ const useTableColumns = () => {
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const isDisabled = row.original.isPublish; // Check the isPublish value const isDisabled = row.original.isPublish;
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild disabled={isDisabled}> <DropdownMenuTrigger asChild disabled={isDisabled}>
@ -128,7 +148,7 @@ const useTableColumns = () => {
className={`bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent ${ className={`bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent ${
isDisabled ? "cursor-not-allowed opacity-50" : "" isDisabled ? "cursor-not-allowed opacity-50" : ""
}`} }`}
disabled={isDisabled} // Disable button if isPublish is true disabled={isDisabled}
> >
<span className="sr-only">Open menu</span> <span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" /> <MoreVertical className="h-4 w-4 text-default-800" />
@ -142,7 +162,7 @@ const useTableColumns = () => {
className={`p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none ${ className={`p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none ${
isDisabled ? "cursor-not-allowed opacity-50" : "" isDisabled ? "cursor-not-allowed opacity-50" : ""
}`} }`}
disabled={isDisabled} // Disable dropdown item if isPublish is true disabled={isDisabled}
> >
<MoveDownRight className="w-4 h-4 me-1.5" /> <MoveDownRight className="w-4 h-4 me-1.5" />
Pindah Ke Mediahub Pindah Ke Mediahub

View File

@ -823,7 +823,7 @@ const FilterPage = () => {
<Link href={`/image/detail/${image?.slug}`}> <Link href={`/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">
<ImageBlurry {/* <ImageBlurry
src={ src={
image?.smallThumbnailLink || image?.smallThumbnailLink ||
image?.thumbnailLink image?.thumbnailLink
@ -834,6 +834,16 @@ const FilterPage = () => {
width: "100%", width: "100%",
height: "100%", height: "100%",
}} }}
/> */}
<Image
width={2560}
height={1440}
src={
image?.smallThumbnailLink ||
image?.thumbnailLink
}
alt={image?.title}
className="w-full h-full object-cover rounded-t-lg"
/> />
</div> </div>
<div className="flex flex-row items-center gap-2 text-[10px] mx-1 mt-2"> <div className="flex flex-row items-center gap-2 text-[10px] mx-1 mt-2">

View File

@ -833,7 +833,7 @@ const FilterPage = () => {
<Link href={`/video/detail/${video?.slug}`}> <Link href={`/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">
<ImageBlurry {/* <ImageBlurry
src={ src={
video?.smallThumbnailLink || video?.smallThumbnailLink ||
video?.thumbnailLink video?.thumbnailLink
@ -844,6 +844,16 @@ const FilterPage = () => {
width: "100%", width: "100%",
height: "100%", height: "100%",
}} }}
/> */}
<Image
width={2560}
height={1440}
src={
video?.smallThumbnailLink ||
video?.thumbnailLink
}
alt={video?.title}
className="w-full h-full object-cover rounded-t-lg"
/> />
</div> </div>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2"> <div className="flex flex-row items-center gap-2 text-[10px] mx-2">

View File

@ -167,6 +167,7 @@ export default function FormImage() {
"image/png": [], "image/png": [],
"image/jpg": [], "image/jpg": [],
}, },
multiple: true,
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
const validFiles = acceptedFiles const validFiles = acceptedFiles
.filter( .filter(
@ -187,8 +188,11 @@ export default function FormImage() {
return; return;
} }
setFiles(validFiles); setFiles((prev) => {
setValue("files", validFiles); const next = [...prev, ...validFiles];
setValue("files", next, { shouldDirty: true });
return next;
});
}, },
}); });
@ -1167,12 +1171,13 @@ export default function FormImage() {
render={({ field: { onChange, value } }) => render={({ field: { onChange, value } }) =>
isLoadingData ? ( isLoadingData ? (
<div className="flex justify-center items-center h-40"> <div className="flex justify-center items-center h-40">
<p className="text-gray-500"> <p className="text-gray-500 dark:text-black">
Loading Proses Data... Loading Proses Data...
</p> </p>
</div> </div>
) : ( ) : (
<CustomEditor <CustomEditor
className="dark:text-black"
onChange={(value: any) => { onChange={(value: any) => {
onChange(value); onChange(value);
setEditorContent(value); setEditorContent(value);

View File

@ -3,7 +3,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSearchParams, useParams } from "next/navigation"; import { useSearchParams, useParams } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; // pastikan path-nya sesuai
import { import {
Carousel, Carousel,
CarouselContent, CarouselContent,
@ -12,7 +11,7 @@ import {
CarouselPrevious, CarouselPrevious,
} from "@/components/ui/carousel"; } from "@/components/ui/carousel";
import { getIndeksData, getIndeksDataFilter } from "@/service/landing/landing"; import { getIndeksData, getIndeksDataFilter } from "@/service/landing/landing";
import { usePathname, useRouter } from "@/i18n/routing"; import { Link, usePathname, useRouter } from "@/i18n/routing";
import { loading } from "@/lib/swal"; import { loading } from "@/lib/swal";
import { getOnlyMonthAndYear } from "@/utils/globals"; import { getOnlyMonthAndYear } from "@/utils/globals";
@ -34,8 +33,8 @@ export default function IndeksCarouselComponent(props: {
const asPath = usePathname(); const asPath = usePathname();
const params = useParams(); const params = useParams();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [indeksData, setIndeksData] = useState<any[]>([]); // ✅ State untuk menyimpan konten data const [indeksData, setIndeksData] = useState<any[]>([]);
const [newContent, setNewContent] = useState<any[]>([]); // Optional, bisa jadi redundant const [newContent, setNewContent] = useState<any[]>([]);
const [totalData, setTotalData] = useState<number>(0); const [totalData, setTotalData] = useState<number>(0);
const [totalPage, setTotalPage] = useState<number>(0); const [totalPage, setTotalPage] = useState<number>(0);
const [totalContent, setTotalContent] = useState<number>(0); const [totalContent, setTotalContent] = useState<number>(0);
@ -111,81 +110,53 @@ export default function IndeksCarouselComponent(props: {
]); ]);
async function initFetch() { async function initFetch() {
if (asPath?.includes("/polda/") == true) { const filter =
if (asPath?.split("/")[2] !== "[polda_name]") { categoryFilter?.length > 0
const filter = ? categoryFilter?.sort().join(",")
categoryFilter?.length > 0 : categorie || "";
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title ?? "";
const filterGroup = group == undefined ? asPath.split("/")[2] : group; const month = monthYearFilter
loading(); ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
const response = await getIndeksDataFilter( : "";
"4", const year = monthYearFilter
name, ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
filter, : "";
12,
0,
sortByOpt,
"",
"",
filterGroup,
startDateString,
endDateString,
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)
?.split("/")[0]
?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
);
close();
const data = response?.data?.data;
const contentData = data?.content;
setNewContent(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
setTotalContent(response?.data?.data?.totalElements);
}
} else {
const filter =
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; // Gunakan group hanya jika asPath mengandung /polda/ atau /satker/, selain itu kosongkan
let filterGroup = "";
if (asPath.includes("/polda/") || asPath.includes("/satker/")) {
filterGroup = group ?? asPath.split("/")[2];
}
try {
loading(); loading();
const response = await getIndeksDataFilter( const response = await getIndeksDataFilter(
"4", "4", // type
name, name, // title
filter, filter, // kategori
12, 12, // size
0, 0, // page
sortByOpt, sortByOpt, // sortBy
"",
"",
"", "",
"", // provinceId, cityId
filterGroup, // group
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter month,
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") year
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
const data = response?.data?.data; const data = response?.data?.data;
const contentData = data?.content; const contentData = data?.content;
setNewContent(contentData || []);
setNewContent(contentData); setTotalData(data?.totalElements || 0);
setTotalData(data?.totalElements); setTotalPage(data?.totalPages || 0);
setTotalPage(data?.totalPages); setTotalContent(data?.totalElements || 0);
setTotalContent(response?.data?.data?.totalElements); } catch (error) {
close();
console.error("Error fetching indeks data:", error);
} }
} }
@ -230,7 +201,7 @@ export default function IndeksCarouselComponent(props: {
</Carousel> </Carousel>
<div className="flex justify-center mt-1 mb-6"> <div className="flex justify-center mt-1 mb-6">
<Link <Link
href={`${prefixPath}/image`} href={`${prefixPath}/indeks`}
className="border border-red-500 text-red-500 hover:bg-red-500 text-sm hover:text-white px-4 py-2 rounded transition duration-200" className="border border-red-500 text-red-500 hover:bg-red-500 text-sm hover:text-white px-4 py-2 rounded transition duration-200"
> >
Lihat Semua Lihat Semua

View File

@ -726,17 +726,16 @@ const Navbar = () => {
{/* Inbox */} {/* Inbox */}
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<a className="cursor-pointer" onClick={() => test()}> <a
className="cursor-pointer text-black dark:text-white"
onClick={() => test()}
>
{" "} {" "}
<Icon <Icon icon="basil:envelope-outline" width="30" />
icon="basil:envelope-outline"
color="black"
width="30"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
className=" p-0 h-32 flex flex-col mt-2" className="overflow-y-scroll p-0 h-full flex flex-col mt-2"
align="end" align="end"
> >
<Tabs <Tabs
@ -1214,6 +1213,15 @@ const Navbar = () => {
</a> </a>
</div> </div>
{/* Languange */}
<div
className={`${
isHidden ? "hidden" : "custom-lg-button:flex"
} relative text-left`}
>
<LocalSwitcher />
</div>
<div className="relative inline-block mx-3 text-left"> <div className="relative inline-block mx-3 text-left">
<ThemeSwitcher /> <ThemeSwitcher />

View File

@ -155,7 +155,7 @@ const NewContent = (props: { group: string; type: string }) => {
{[ {[
{ label: t("image", { defaultValue: "Image" }), value: "image" }, { label: t("image", { defaultValue: "Image" }), value: "image" },
{ label: t("video", { defaultValue: "Video" }), value: "video" }, { label: t("video", { defaultValue: "Video" }), value: "video" },
{ label: t("text", { defaultValue: "Text" }), value: "text" }, { label: t("text", { defaultValue: "Text" }), value: "document" },
{ label: t("audio", { defaultValue: "Audio" }), value: "audio" }, { label: t("audio", { defaultValue: "Audio" }), value: "audio" },
].map((tab) => ( ].map((tab) => (
<TabsTrigger <TabsTrigger
@ -591,11 +591,11 @@ const NewContent = (props: { group: string; type: string }) => {
</div> </div>
)} )}
</div> </div>
{/* <div className="flex items-center flex-row justify-center"> <div className="flex items-center flex-row justify-center mt-3">
<div onClick={() => router.push(prefixPath + `/${selectedTab}/filter?sortBy=${props.type}`)} className="cursor-pointer border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523]"> <div onClick={() => router.push(prefixPath + `/${selectedTab}/filter?sortBy=${props.type}`)} className="cursor-pointer border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523] hover:text-white hover:bg-[#bb3523]">
{t("seeAll", { defaultValue: "See All" })} {t("seeAll", { defaultValue: "See All" })}
</div> </div>
</div> */} </div>
</Reveal> </Reveal>
</div> </div>
); );

View File

@ -68,6 +68,11 @@ export async function getMediaBlastCampaignList() {
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }
export async function deleteMediaBlastCampaign(id: string | number): Promise<any> {
const url = `media/blast/campaign?id=${id}`;
return httpDeleteInterceptor({ url });
}
export async function detailMediaSummary(id: string) { export async function detailMediaSummary(id: string) {
const url = `media?id=${id}&isSummary=true`; const url = `media?id=${id}&isSummary=true`;
return httpGetInterceptor(url); return httpGetInterceptor(url);