fix: detail article in admin page

This commit is contained in:
Sabda Yagra 2025-10-14 15:17:22 +07:00
parent 2e86d97aee
commit 0deb587a82
13 changed files with 2171 additions and 1812 deletions

View File

@ -77,12 +77,21 @@ const useTableColumns = () => {
return <span className="whitespace-nowrap">{formattedDate}</span>; return <span className="whitespace-nowrap">{formattedDate}</span>;
}, },
}, },
// {
// accessorKey: "creatorName",
// header: "Creator Group",
// cell: ({ row }) => (
// <span className="whitespace-nowrap">
// {row.getValue("creatorName") || row.getValue("createdByName")}
// </span>
// ),
// },
{ {
accessorKey: "creatorName", accessorKey: "creatorName",
header: "Creator Group", header: "Creator Group",
cell: ({ row }) => ( cell: ({ row }) => (
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
{row.getValue("creatorName") || row.getValue("createdByName")} {row.original.creatorName || row.original.createdByName || "-"}
</span> </span>
), ),
}, },

View File

@ -1,9 +1,11 @@
import FormAudioDetail from "@/components/form/content/audio/audio-detail-form"; import FormAudioDetail from "@/components/form/content/audio/audio-detail-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
const AudioDetailPage = async () => { const AudioDetailPage = async () => {
return ( return (
<div> <div>
<div className="space-y-4"> <SiteBreadcrumb />
<div className="space-y-4 bg-slate-100">
<FormAudioDetail /> <FormAudioDetail />
</div> </div>
</div> </div>

View File

@ -77,12 +77,21 @@ const useTableColumns = () => {
return <span className="whitespace-nowrap">{formattedDate}</span>; return <span className="whitespace-nowrap">{formattedDate}</span>;
}, },
}, },
// {
// accessorKey: "creatorName",
// header: "Creator Group",
// cell: ({ row }) => (
// <span className="whitespace-nowrap">
// {row.getValue("creatorName") || row.getValue("createdByName")}
// </span>
// ),
// },
{ {
accessorKey: "creatorName", accessorKey: "creatorName",
header: "Creator Group", header: "Creator Group",
cell: ({ row }) => ( cell: ({ row }) => (
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
{row.getValue("creatorName") || row.getValue("createdByName")} {row.original.creatorName || row.original.createdByName || "-"}
</span> </span>
), ),
}, },

View File

@ -66,7 +66,6 @@ import useTableColumns from "./columns";
const TableTeks = () => { const TableTeks = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
@ -76,18 +75,14 @@ const TableTeks = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50"); const [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: Number(showData), pageSize: Number(showData),
}); });
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [categories, setCategories] = React.useState<any[]>([]); const [categories, setCategories] = React.useState<any[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<number[]>( const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[] []
@ -99,7 +94,6 @@ const TableTeks = () => {
const [filterByCreator, setFilterByCreator] = React.useState(""); const [filterByCreator, setFilterByCreator] = React.useState("");
const [filterBySource, setFilterBySource] = React.useState(""); const [filterBySource, setFilterBySource] = React.useState("");
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState(""); const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
@ -179,13 +173,12 @@ const TableTeks = () => {
: ""; : "";
try { try {
// Using the new interface-based approach for video content
const filters: ArticleFilters = { const filters: ArticleFilters = {
page: page, page,
totalPage: Number(showData), totalPage: Number(showData),
title: search || undefined, title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined, categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 3, typeId: 3, // 🔹 text content
statusId: statusId:
statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
startDate: formattedStartDate || undefined, startDate: formattedStartDate || undefined,
@ -194,14 +187,17 @@ const TableTeks = () => {
const res = await listArticlesWithFilters(filters); const res = await listArticlesWithFilters(filters);
const data = res?.data?.data; const data = res?.data?.data;
const meta = res?.data?.meta;
if (Array.isArray(data)) { if (Array.isArray(data)) {
data.forEach((item: any, index: number) => { data.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1; item.no = (page - 1) * Number(showData) + index + 1;
}); });
setDataTable(data); setDataTable(data);
setTotalData(data.length); setTotalData(meta?.count ?? data.length);
setTotalPage(Math.ceil(data.length / Number(showData))); setTotalPage(
meta?.totalPage ?? Math.ceil(data.length / Number(showData))
);
} else if (Array.isArray(data?.content)) { } else if (Array.isArray(data?.content)) {
const contentData = data.content; const contentData = data.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -218,7 +214,7 @@ const TableTeks = () => {
setTotalPage(1); setTotalPage(1);
} }
} catch (error) { } catch (error) {
console.error("Error fetching tasks:", error); console.error("Error fetching text content:", error);
setDataTable([]); setDataTable([]);
setTotalData(0); setTotalData(0);
setTotalPage(1); setTotalPage(1);

View File

@ -1,10 +1,11 @@
import FormTeks from "@/components/form/content/document/teks-form"; import FormTeks from "@/components/form/content/document/teks-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
const TeksCreatePage = async () => { const TeksCreatePage = async () => {
return ( return (
<div> <div>
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="space-y-4"> <div className="space-y-4 bg-slate-100">
<FormTeks /> <FormTeks />
</div> </div>
</div> </div>

View File

@ -1,10 +1,11 @@
import FormTeksDetail from "@/components/form/content/document/teks-detail-form"; import FormTeksDetail from "@/components/form/content/document/teks-detail-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
const TeksDetailPage = async () => { const TeksDetailPage = async () => {
return ( return (
<div> <div>
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="space-y-4"> <div className="space-y-4 bg-slate-100">
<FormTeksDetail /> <FormTeksDetail />
</div> </div>
</div> </div>

View File

@ -21,8 +21,6 @@ import { Checkbox } from "@/components/ui/checkbox";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { import {
getArticleDetail, getArticleDetail,
getTagsBySubCategoryId,
listEnableCategory,
publishMedia, publishMedia,
submitApproval, submitApproval,
} from "@/service/content/content"; } from "@/service/content/content";
@ -47,7 +45,6 @@ import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import SuggestionModal from "@/components/modal/suggestions-modal";
import { formatDateToIndonesian } from "@/utils/globals"; import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal"; import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
import { listArticleCategories } from "@/service/content"; import { listArticleCategories } from "@/service/content";
@ -62,19 +59,13 @@ const ViewEditor = dynamic(() => import("@/components/editor/view-editor"), {
ssr: false, ssr: false,
}); });
type Category = { type Category = { id: string; title: string };
id: string;
title: string;
};
type FileType = { type FileType = {
id: number; id: number;
fileName: string; fileName: string;
fileUrl?: string; fileUrl?: string;
fileThumbnail?: string; fileThumbnail?: string;
format?: string;
}; };
type Detail = { type Detail = {
id: number; id: number;
title: string; title: string;
@ -137,46 +128,28 @@ export default function FormVideoDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
const userLevelName = Cookies.get("state"); const userLevelName = Cookies.get("state");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [detail, setDetail] = useState<Detail | null>(null); const [detail, setDetail] = useState<Detail | null>(null);
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null); const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [approval, setApproval] = useState<any>(); const [approval, setApproval] = useState<any>();
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]); const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [filePlacements, setFilePlacements] = useState<string[][]>([]); const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [refresh, setRefresh] = useState(false);
const [detailVideos, setDetailVideos] = useState<string[]>([]);
const [selectedCategory, setSelectedCategory] = useState<string>("");
const { const { control } = useForm({ resolver: zodResolver(videoSchema) });
control,
formState: { errors },
} = useForm({
resolver: zodResolver(videoSchema),
});
const getStatusName = (statusId: number): string => {
const statusMap: { [key: number]: string } = {
1: "Menunggu Review",
2: "Diterima",
3: "Minta Update",
4: "Ditolak",
};
return statusMap[statusId] || "Unknown";
};
useEffect(() => { useEffect(() => {
if (userLevelId == "216" && roleId == "3") { if (userLevelId == "216" && roleId == "3") setIsUserMabesApprover(true);
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]); }, [userLevelId, roleId]);
useEffect(() => { useEffect(() => {
@ -193,29 +166,16 @@ export default function FormVideoDetail() {
try { try {
const response = await getArticleDetail(Number(id)); const response = await getArticleDetail(Number(id));
const details = response?.data?.data; const details = response?.data?.data;
setSelectedCategory(String(details.categories[0].id));
const mappedDetail: Detail = { const mappedDetail: Detail = {
...details, ...details,
statusId: details?.statusId,
categoryId: details?.categories?.[0]?.id, categoryId: details?.categories?.[0]?.id,
htmlDescription: details?.htmlDescription, htmlDescription: details?.htmlDescription,
statusName: getStatusName(details?.statusId),
uploadedById: details?.createdById, uploadedById: details?.createdById,
files: details?.files || [], files: details?.files || [],
categories: details?.categories || [],
}; };
const mappedFiles =
details?.files?.map((f: any) => ({
id: f.id,
fileName: f.fileName,
fileUrl: f.fileUrl,
fileThumbnail: f.fileThumbnail,
format: f.format || "mp4",
})) || [];
setDetail(mappedDetail); setDetail(mappedDetail);
setFiles(mappedFiles); setFiles(details?.files || []);
setDetailVideos(mappedFiles.map((f: any) => f.fileUrl || ""));
const approvals = await getDataApprovalByMediaUpload(mappedDetail.id); const approvals = await getDataApprovalByMediaUpload(mappedDetail.id);
setApproval(approvals?.data?.data); setApproval(approvals?.data?.data);
} catch (err) { } catch (err) {
@ -223,33 +183,32 @@ export default function FormVideoDetail() {
} }
} }
fetchDetail(); fetchDetail();
}, [id, refresh]); }, [id]);
const getStatusName = (statusId: number) => {
const map: Record<number, string> = {
1: "Menunggu Review",
2: "Diterima",
3: "Minta Update",
4: "Ditolak",
};
return map[statusId] || "Unknown";
};
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
); );
}; };
const actionApproval = (e: string) => { const actionApproval = (type: string) => {
const temp = []; const placements = files.map(() => []);
for (const element of files) temp.push([]); setFilePlacements(placements);
setFilePlacements(temp); setStatus(type);
setStatus(e);
setDescription(""); setDescription("");
setModalOpen(true); setModalOpen(true);
}; };
const submit = async () => {
if (
(description?.length > 1 && Number(status) == 3) ||
Number(status) == 2 ||
Number(status) == 4
) {
await save();
}
};
const save = async () => { const save = async () => {
const data = { const data = {
action: status == "2" ? "approve" : status == "3" ? "revision" : "reject", action: status == "2" ? "approve" : status == "3" ? "revision" : "reject",
@ -257,8 +216,8 @@ export default function FormVideoDetail() {
}; };
setModalOpen(false); setModalOpen(false);
loading(); loading();
const response = await submitApproval(id, data); const res = await submitApproval(id, data);
if (response?.error) return error(response.message); if (res?.error) return error(res.message);
close(); close();
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
@ -272,23 +231,15 @@ export default function FormVideoDetail() {
successCallback(); successCallback();
}; };
if (!detail) { if (!detail) return <div className="p-10 text-gray-500">Memuat data...</div>;
return (
<div className="text-center py-20 text-gray-500">
Memuat detail video...
</div>
);
}
return ( return (
<form> <form>
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg"> <div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
{/* LEFT SIDE */}
<Card className="w-full lg:w-8/12 m-2"> <Card className="w-full lg:w-8/12 m-2">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Video</p> <p className="text-lg font-semibold mb-3">Form Video</p>
{/* Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Title</Label> <Label>Title</Label>
<Controller <Controller
@ -296,31 +247,22 @@ export default function FormVideoDetail() {
name="title" name="title"
render={({ field }) => ( render={({ field }) => (
<Input <Input
type="text" value={detail.title}
value={detail?.title}
onChange={field.onChange} onChange={field.onChange}
placeholder="Enter Title" disabled
/> />
)} )}
/> />
{/* {errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)} */}
</div> </div>
{/* Category */}
<div className="py-3 w-full space-y-2"> <div className="py-3 w-full space-y-2">
<Label>Category</Label> <Label>Category</Label>
<Select <Select disabled value={String(detail.categoryId)}>
value={selectedCategory}
onValueChange={setSelectedCategory}
disabled
>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Pilih kategori" /> <SelectValue placeholder="Pilih kategori" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{categories?.map((cat) => ( {categories.map((cat) => (
<SelectItem key={cat.id} value={String(cat.id)}> <SelectItem key={cat.id} value={String(cat.id)}>
{cat.title} {cat.title}
</SelectItem> </SelectItem>
@ -328,29 +270,12 @@ export default function FormVideoDetail() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
{/* <div className="py-3 space-y-2">
<Label>Category</Label>
<Select disabled value={String(detail.categoryId)}>
<SelectTrigger>
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((cat) => (
<SelectItem key={cat.id} value={String(cat.id)}>
{cat.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div> */}
{/* Description */}
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label>Description</Label> <Label>Description</Label>
<ViewEditor initialData={detail.htmlDescription} /> <ViewEditor initialData={detail.htmlDescription} />
</div> </div>
{/* Video Files */}
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-xl">File Media</Label> <Label className="text-xl">File Media</Label>
<Swiper <Swiper
@ -391,24 +316,19 @@ export default function FormVideoDetail() {
</div> </div>
</Card> </Card>
{/* RIGHT SIDE */}
<div className="w-full lg:w-4/12 m-2"> <div className="w-full lg:w-4/12 m-2">
<Card className="pb-3"> <Card className="pb-3">
<div className="px-3 py-3"> <div className="px-3 py-3">
<Label>Creator</Label> <Label>Creator</Label>
<Input <Input value={detail.createdByName} disabled />
value={detail.createdByName}
disabled
placeholder="Creator"
/>
</div> </div>
<div className="mt-3 px-3 space-y-2"> <div className="mt-3 px-3 space-y-2">
<Label>Preview</Label> <Label>Preview</Label>
<Card className="mt-2 w-fit"> <Card className="mt-2 w-fit">
<img <img
src={detail.thumbnailUrl} src={detail.thumbnailUrl || detail.thumbnailLink}
alt="Thumbnail" alt="Thumbnail Gambar Utama"
className="h-[200px] rounded" className="h-[200px] rounded"
/> />
</Card> </Card>
@ -417,10 +337,10 @@ export default function FormVideoDetail() {
<div className="px-3 py-3"> <div className="px-3 py-3">
<Label>Tags</Label> <Label>Tags</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail.tags?.split(",").map((tag: string, index: number) => ( {detail.tags?.split(",").map((tag, i) => (
<Badge <Badge
key={index} key={i}
className="border rounded-md bg-black text-white px-2 py-2" className="border bg-black text-white px-2 py-2"
> >
{tag.trim()} {tag.trim()}
</Badge> </Badge>
@ -436,24 +356,18 @@ export default function FormVideoDetail() {
id={String(target)} id={String(target)}
checked={selectedPublishers.includes(target)} checked={selectedPublishers.includes(target)}
onChange={() => handleCheckboxChange(target)} onChange={() => handleCheckboxChange(target)}
className="border"
/> />
<Label htmlFor={String(target)}> <Label htmlFor={String(target)}>
{target === 5 ? "UMUM" : target === 6 ? "JOURNALIS" : ""} {target === 5 ? "UMUM" : "JOURNALIS"}
</Label> </Label>
</div> </div>
))} ))}
</div> </div>
{/* <SuggestionModal
id={Number(id)}
numberOfSuggestion={detail?.numberOfSuggestion}
/> */}
<div className="px-3 py-3 border mx-3"> <div className="px-3 py-3 border mx-3">
<p>Information:</p> <p>Information:</p>
<p className="text-sm text-slate-400"> <p className="text-sm text-slate-400">
{detail.statusName || getStatusName(detail.statusId)} {detail.statusId && getStatusName(detail.statusId)}
</p> </p>
<p>Komentar</p> <p>Komentar</p>
<p>{approval?.message}</p> <p>{approval?.message}</p>
@ -471,6 +385,108 @@ export default function FormVideoDetail() {
</Button> </Button>
</div> </div>
)} )}
{(Number(detail.needApprovalFromLevel || 0) ==
Number(userLevelId) ||
(detail.isPublish === false && detail.statusId == 1)) &&
Number(detail.uploadedById || detail.createdById) !=
Number(userId) ? (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" /> Reject
</Button>
</div>
) : null}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="max-h-[600px] overflow-y-auto">
<DialogHeader>
<DialogTitle>Leave Comment</DialogTitle>
</DialogHeader>
{status === "2" &&
files.map((file, i) => (
<div key={i} className="flex items-center gap-4 mb-3">
<video
src={file.fileUrl}
className="w-[200px] h-[120px]"
controls
/>
<span>{file.fileName}</span>
</div>
))}
<Textarea
placeholder="Tulis komentar Anda..."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<div className="flex flex-wrap gap-2 mt-2">
{status === "3" || status === "4" ? (
<>
{[
"Kualitas media kurang baik",
"Deskripsi kurang lengkap",
"Judul kurang tepat",
].map((text) => (
<Button
key={text}
size="sm"
variant={description === text ? "default" : "outline"}
onClick={() => setDescription(text)}
>
{text}
</Button>
))}
</>
) : (
<>
{["Konten sangat bagus", "Konten menarik"].map((text) => (
<Button
key={text}
size="sm"
variant={description === text ? "default" : "outline"}
onClick={() => setDescription(text)}
>
{text}
</Button>
))}
</>
)}
</div>
<DialogFooter className="mt-4 flex justify-end gap-3">
<Button onClick={() => save()} color="primary">
Submit
</Button>
<Button
color="destructive"
onClick={() => setModalOpen(false)}
>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card> </Card>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -256,8 +256,11 @@ export default function FormAudioDetail() {
setDetail(details); setDetail(details);
setMain({ setMain({
type: details?.fileType.name, type:
url: details?.files[0]?.url, details?.fileType?.name ||
details?.fileTypeName || // kalau backend pakai nama ini
details?.typeName || // atau nama alternatif
"Audio", // fallback aman url: details?.files[0]?.url,
names: details?.files[0]?.fileName, names: details?.files[0]?.fileName,
format: details?.files[0]?.format, format: details?.files[0]?.format,
}); });
@ -448,8 +451,8 @@ export default function FormAudioDetail() {
return ( return (
<form> <form>
{detail !== undefined ? ( {detail !== undefined ? (
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12 m-2">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Audio</p> <p className="text-lg font-semibold mb-3">Form Audio</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -533,31 +536,12 @@ export default function FormAudioDetail() {
</div> </div>
</div> </div>
</Card> </Card>
<div className="w-full lg:w-4/12"> <div className="w-full lg:w-4/12 m-2">
<Card className="pb-3"> <Card className="pb-3">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2">
<Label>Creator</Label> <Label>Creator</Label>
<Controller <Input value={detail.createdByName} disabled />
control={control}
name="creatorName"
render={({ field }) => (
<Input
type="text"
value={detail?.creatorName}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div> </div>
</div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tags</Label> <Label>Tags</Label>
@ -567,7 +551,7 @@ export default function FormAudioDetail() {
.map((tag: string, index: number) => ( .map((tag: string, index: number) => (
<Badge <Badge
key={index} key={index}
className="border rounded-md px-2 py-2" className="border rounded-md bg-black text-white px-2 py-2"
> >
{tag.trim()} {tag.trim()}
</Badge> </Badge>
@ -576,40 +560,26 @@ export default function FormAudioDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6 space-y-2"> <div className="flex flex-col gap-2 space-y-2">
<Label>Publish Target</Label> <Label>Publish Target</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="4"
checked={selectedPublishers.includes(5)} checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)} onChange={() => handleCheckboxChange(5)}
className="border"
/> />
<Label htmlFor="5">UMUM</Label> <Label htmlFor="5">UMUM</Label>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="6" id="5"
checked={selectedPublishers.includes(6)} checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)} onChange={() => handleCheckboxChange(6)}
className="border"
/> />
<Label htmlFor="6">JOURNALIS</Label> <Label htmlFor="6">JOURNALIS</Label>
</div> </div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
</div>
</div> </div>
</div> </div>
<SuggestionModal <SuggestionModal

View File

@ -66,6 +66,11 @@ import FileTextPreview from "../file-preview-text";
import FileTextThumbnail from "../file-text-thumbnail"; import FileTextThumbnail from "../file-text-thumbnail";
import { listArticleCategories } from "@/service/content"; import { listArticleCategories } from "@/service/content";
type Option = {
id: string;
label: string;
};
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z
@ -120,13 +125,11 @@ export default function FormTeksDetail() {
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
console.log(id); console.log(id);
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type ImageSchema = z.infer<typeof imageSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -146,11 +149,16 @@ export default function FormTeksDetail() {
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]); const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isMabesApprover, setIsMabesApprover] = useState(false); const [isMabesApprover, setIsMabesApprover] = useState(false);
const [filePlacements, setFilePlacements] = useState<string[][]>([]); const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false); const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [approval, setApproval] = useState<any>(); const [approval, setApproval] = useState<any>();
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "4", label: "UMUM" },
{ id: "5", label: "JOURNALIS" },
];
let fileTypeId = "3"; let fileTypeId = "3";
@ -218,43 +226,63 @@ export default function FormTeksDetail() {
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
if (id) { if (!id) return;
try {
const response = await getArticleDetail(Number(id)); const response = await getArticleDetail(Number(id));
const details = response?.data?.data; const details = response?.data?.data;
console.log("detail", details); if (!details) return;
setSelectedCategory(String(details.categories[0].id));
setFiles(details?.files); // ✅ Aman untuk categories
const firstCategoryId =
details?.categories && details.categories.length > 0
? String(details.categories[0].id)
: "";
setSelectedCategory(firstCategoryId);
setFiles(details?.files || []);
setDetail(details); setDetail(details);
// ✅ Aman untuk fileType
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType?.name || "Unknown",
url: details?.files[0]?.url, url: details?.files?.[0]?.url || "",
names: details?.files[0]?.fileName, names: details?.files?.[0]?.fileName || "",
format: details?.files[0]?.format, format: details?.files?.[0]?.format || "",
}); });
if (details.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details.publishedForObject.map( const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id (obj: any) => obj.id
); );
setSelectedPublishers(publisherIds); setSelectedPublishers(publisherIds);
} }
// Set the selected target to the category ID from details setSelectedTarget(String(details?.category?.id || ""));
setSelectedTarget(String(details.category.id));
const filesData = details?.files || [];
const fileUrls = filesData.map((file: any) => {
const ext = file?.fileName?.includes(".")
? "." + file.fileName.split(".").pop()
: file?.type || "";
return {
url: file?.url || file?.secondaryUrl || "default-image.jpg",
format: ext?.toLowerCase(),
fileName: file?.fileName || "Unknown file",
type: file?.type || "",
};
});
const filesData = details.files || [];
const fileUrls = filesData.map((file: any) => ({
url: file.secondaryUrl || "default-image.jpg",
format: file.format,
fileName: file.fileName,
}));
setDetailThumb(fileUrls); setDetailThumb(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id); const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data); setApproval(approvals?.data?.data);
} catch (err) {
console.error("Error loading article details:", err);
} }
} }
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
@ -392,15 +420,15 @@ export default function FormTeksDetail() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/admin/content/document"); router.push("/admin/content/text");
}); });
}; };
return ( return (
<form> <form>
{detail !== undefined ? ( {detail !== undefined ? (
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12 m-2">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Text</p> <p className="text-lg font-semibold mb-3">Form Text</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -463,7 +491,7 @@ export default function FormTeksDetail() {
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-xl">File Media </Label> <Label className="text-xl">File Media</Label>
<div className="w-full"> <div className="w-full">
<Swiper <Swiper
thumbs={{ swiper: thumbsSwiper }} thumbs={{ swiper: thumbsSwiper }}
@ -498,29 +526,11 @@ export default function FormTeksDetail() {
</div> </div>
</div> </div>
</Card> </Card>
<div className="w-full lg:w-4/12"> <div className="w-full lg:w-4/12 m-2">
<Card className="pb-3"> <Card className="pb-3">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2">
<Label>Creator</Label> <Label>Creator</Label>
<Controller <Input value={detail.createdByName} disabled />
control={control}
name="creatorName"
render={({ field }) => (
<Input
type="text"
value={detail?.creatorName}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div> </div>
{/* <div className="mt-3 px-3"> {/* <div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label> <Label>Pratinjau Gambar Utama</Label>
@ -541,7 +551,7 @@ export default function FormTeksDetail() {
.map((tag: string, index: number) => ( .map((tag: string, index: number) => (
<Badge <Badge
key={index} key={index}
className="border rounded-md px-2 py-2" className="border rounded-md bg-black text-white px-2 py-2"
> >
{tag.trim()} {tag.trim()}
</Badge> </Badge>
@ -550,40 +560,26 @@ export default function FormTeksDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6 space-y-2"> <div className="flex flex-col gap-2 space-y-2">
<Label>Publish Target</Label> <Label>Publish Target</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="4"
checked={selectedPublishers.includes(5)} checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)} onChange={() => handleCheckboxChange(5)}
className="border"
/> />
<Label htmlFor="5">UMUM</Label> <Label htmlFor="5">UMUM</Label>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="6" id="5"
checked={selectedPublishers.includes(6)} checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)} onChange={() => handleCheckboxChange(6)}
className="border"
/> />
<Label htmlFor="6">JOURNALIS</Label> <Label htmlFor="6">JOURNALIS</Label>
</div> </div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
</div>
</div> </div>
</div> </div>
<SuggestionModal <SuggestionModal
@ -821,10 +817,10 @@ export default function FormTeksDetail() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</Card> </Card>
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? ( {/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
Number(detail?.uploadedById) == Number(userId) ? ( Number(detail?.uploadedById) == Number(userId) ? (
"" ""
) : ( ) : ( */}
<div className="flex flex-col gap-2 p-3"> <div className="flex flex-col gap-2 p-3">
<Button <Button
onClick={() => actionApproval("2")} onClick={() => actionApproval("2")}
@ -849,10 +845,10 @@ export default function FormTeksDetail() {
Reject Reject
</Button> </Button>
</div> </div>
) {/* )
) : ( ) : (
"" ""
)} )} */}
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -144,10 +144,8 @@ export default function FormTeks() {
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
const options: Option[] = [ const options: Option[] = [
{ id: "all", label: "SEMUA" }, { id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" }, { id: "4", label: "UMUM" },
{ id: "6", label: "JOURNALIS" }, { id: "5", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
]; ];
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
@ -638,12 +636,11 @@ export default function FormTeks() {
} }
if (id == undefined) { if (id == undefined) {
// New Articles API request data structure
const articleData: CreateArticleData = { const articleData: CreateArticleData = {
aiArticleId: 0, // default 0 aiArticleId: 0,
categoryIds: selectedCategory.toString(), categoryIds: selectedCategory.toString(),
createdAt: formatDateForBackend(new Date()), // ✅ format sesuai backend createdAt: formatDateForBackend(new Date()),
createdById: Number(userId), // isi dengan userId valid createdById: Number(userId),
description: htmlToString(finalDescription), description: htmlToString(finalDescription),
htmlDescription: finalDescription, htmlDescription: finalDescription,
isDraft: true, isDraft: true,
@ -655,9 +652,8 @@ export default function FormTeks() {
.replace(/[^a-z0-9-]/g, ""), .replace(/[^a-z0-9-]/g, ""),
tags: finalTags, tags: finalTags,
title: finalTitle, title: finalTitle,
typeId: 1, // Image content type typeId: 3,
}; };
// Use new Articles API
const response = await createArticle(articleData); const response = await createArticle(articleData);
console.log("Article Data Submitted:", articleData); console.log("Article Data Submitted:", articleData);
console.log("Article API Response:", response); console.log("Article API Response:", response);
@ -670,16 +666,12 @@ export default function FormTeks() {
); );
return false; return false;
} }
// Get the article ID from the new API response
const articleId = response?.data?.data?.id; const articleId = response?.data?.data?.id;
Cookies.set("idCreate", articleId, { expires: 1 }); Cookies.set("idCreate", articleId, { expires: 1 });
id = articleId; id = articleId;
// Upload files using new article-files API
const formData = new FormData(); const formData = new FormData();
// Add all files to FormData
files.forEach((file, index) => { files.forEach((file, index) => {
formData.append("files", file); formData.append("files", file);
}); });
@ -701,10 +693,9 @@ export default function FormTeks() {
console.log("Files uploaded successfully:", uploadResponse); console.log("Files uploaded successfully:", uploadResponse);
// Upload thumbnail using first file as thumbnail
if (files.length > 0) { if (files.length > 0) {
const thumbnailFormData = new FormData(); const thumbnailFormData = new FormData();
thumbnailFormData.append("files", files[0]); // Use first file as thumbnail thumbnailFormData.append("files", files[0]);
console.log("Uploading thumbnail for article:", articleId); console.log("Uploading thumbnail for article:", articleId);
@ -719,7 +710,6 @@ export default function FormTeks() {
"Thumbnail upload failed:", "Thumbnail upload failed:",
thumbnailResponse.message thumbnailResponse.message
); );
// Don't fail the whole process if thumbnail upload fails
} else { } else {
console.log( console.log(
"Thumbnail uploaded successfully:", "Thumbnail uploaded successfully:",
@ -728,7 +718,6 @@ export default function FormTeks() {
} }
} catch (thumbnailError) { } catch (thumbnailError) {
console.warn("Thumbnail upload error:", thumbnailError); console.warn("Thumbnail upload error:", thumbnailError);
// Don't fail the whole process if thumbnail upload fails
} }
} }
} catch (uploadError) { } catch (uploadError) {
@ -741,7 +730,6 @@ export default function FormTeks() {
return false; return false;
} }
// Show success message
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
text: "Article dan files berhasil disimpan.", text: "Article dan files berhasil disimpan.",
@ -749,7 +737,7 @@ export default function FormTeks() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/admin/content/document"); router.push("/admin/content/text");
}); });
Cookies.remove("idCreate"); Cookies.remove("idCreate");
@ -980,8 +968,8 @@ export default function FormTeks() {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12 m-2">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Document</p> <p className="text-lg font-semibold mb-3">Form Document</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -1428,7 +1416,7 @@ export default function FormTeks() {
{/* Submit Button */} {/* Submit Button */}
</div> </div>
</Card> </Card>
<div className="w-full lg:w-4/12"> <div className="w-full lg:w-4/12 m-2">
<Card className=" h-[500px]"> <Card className=" h-[500px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
@ -1561,6 +1549,7 @@ export default function FormTeks() {
id={option.id} id={option.id}
checked={isChecked} checked={isChecked}
onCheckedChange={handleChange} onCheckedChange={handleChange}
className="border"
/> />
<Label htmlFor={option.id}>{option.label}</Label> <Label htmlFor={option.id}>{option.label}</Label>
</div> </div>

View File

@ -2,8 +2,9 @@ import React from "react";
type FileData = { type FileData = {
url: string; url: string;
format: string; // extension with dot, e.g. ".pdf" format: string; // bisa ".pdf" atau "mp4" atau "image/jpeg"
fileName?: string; fileName?: string;
type?: string; // optional dari API (misal: video/mp4)
}; };
interface FilePreviewProps { interface FilePreviewProps {
@ -11,48 +12,102 @@ interface FilePreviewProps {
} }
const FileTextPreview: React.FC<FilePreviewProps> = ({ file }) => { const FileTextPreview: React.FC<FilePreviewProps> = ({ file }) => {
const format = file.format.toLowerCase(); const format = (file.format || "").toLowerCase();
const type = (file.type || "").toLowerCase();
if ([".jpg", ".jpeg", ".png", ".webp"].includes(format)) { // 🖼️ Gambar
if (
[".jpg", ".jpeg", ".png", ".webp", ".gif"].some((ext) =>
format.includes(ext)
) ||
type.includes("image")
) {
return ( return (
<img <img
className="object-fill h-full w-full rounded-md" className="object-contain h-[500px] w-full rounded-md bg-black"
src={file.url} src={file.url}
alt={file.fileName || "File"} alt={file.fileName || "Image File"}
/> />
); );
} }
if (format === ".pdf") { // 🎬 Video
if (
["mp4", "mov", "avi", ".mp4", ".mov", ".avi"].some((ext) =>
format.includes(ext)
) ||
type.includes("video")
) {
return (
<video
className="object-contain h-[500px] w-full rounded-md bg-black"
controls
>
<source src={file.url} type={type || "video/mp4"} />
Browser Anda tidak mendukung video tag.
</video>
);
}
// 🎧 Audio
if (
["mp3", "wav", "ogg", ".mp3", ".wav", ".ogg"].some((ext) =>
format.includes(ext)
) ||
type.includes("audio")
) {
return (
<div className="flex flex-col items-center justify-center h-[200px] bg-gray-100 rounded-md">
<audio controls className="w-full max-w-[90%]">
<source src={file.url} type={type || "audio/mpeg"} />
Browser Anda tidak mendukung audio tag.
</audio>
</div>
);
}
// 📄 PDF
if (format.includes(".pdf") || type.includes("pdf")) {
return ( return (
<iframe <iframe
className="w-full h-96 rounded-md" className="w-full h-[600px] rounded-md"
src={`https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(file.url)}`} src={`https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(
file.url
)}`}
title={file.fileName || "PDF File"} title={file.fileName || "PDF File"}
/> />
); );
} }
if ([".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx"].includes(format)) { // 🧾 Dokumen Office
if (
[".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx"].some((ext) =>
format.includes(ext)
)
) {
return ( return (
<iframe <iframe
className="w-full h-96 rounded-md" className="w-full h-[600px] rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(file.url)}`} src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
title={file.fileName || "Document"} file.url
)}`}
title={file.fileName || "Office Document"}
/> />
); );
} }
// Fallback → unknown format // 🔗 Default fallback → link download
return ( return (
<div className="text-center py-20">
<a <a
href={file.url} href={file.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="block text-blue-500 underline" className="text-blue-500 underline"
> >
View {file.fileName || "File"} Lihat {file.fileName || "File"}
</a> </a>
</div>
); );
}; };

View File

@ -117,32 +117,52 @@ export default function MediaUpdate() {
} }
} }
// Function to filter data by content type
function filterDataByContentType() {
// if (contentType === "all") { // if (contentType === "all") {
// setFilteredData(dataToRender); // setFilteredData(dataToRender);
// return; // return;
// } // }
const filtered = dataToRender.filter((item) => { // Function to filter data by content type
// Determine content type based on item properties
const hasVideo = item.videoUrl || item.videoPath;
const hasAudio = item.audioUrl || item.audioPath;
const hasImage =
item.smallThumbnailLink || item.thumbnailUrl || item.imageUrl;
const hasText = item.content || item.description;
// function filterDataByContentType() {
// const filtered = dataToRender.filter((item) => {
// // Determine content type based on item properties
// const hasVideo = item.videoUrl || item.videoPath;
// const hasAudio = item.audioUrl || item.audioPath;
// const hasImage =
// item.smallThumbnailLink || item.thumbnailUrl || item.imageUrl;
// const hasText = item.content || item.description;
// switch (contentType) {
// case "audiovisual":
// return hasVideo;
// case "audio":
// return hasAudio && !hasVideo;
// case "foto":
// return hasImage && !hasVideo && !hasAudio;
// case "text":
// return hasText && !hasVideo && !hasAudio && !hasImage;
// default:
// return true;
// }
// });
// setFilteredData(filtered);
// }
function filterDataByContentType() {
const filtered = dataToRender.filter((item) => {
switch (contentType) { switch (contentType) {
case "audiovisual": case "audiovisual":
return hasVideo && (hasAudio || hasImage); return item.typeId === 2; // Video
case "audio": case "audio":
return hasAudio && !hasVideo; return item.typeId === 4; // Audio
case "foto": case "foto":
return hasImage && !hasVideo && !hasAudio; return item.typeId === 1; // Image
case "text": case "text":
return hasText && !hasVideo && !hasAudio && !hasImage; return item.typeId === 3; // Text
default: default:
return true; return true; // Semua jenis
} }
}); });