fix: detail article in admin page
This commit is contained in:
parent
2e86d97aee
commit
0deb587a82
|
|
@ -77,12 +77,21 @@ const useTableColumns = () => {
|
|||
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",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorName") || row.getValue("createdByName")}
|
||||
{row.original.creatorName || row.original.createdByName || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import FormAudioDetail from "@/components/form/content/audio/audio-detail-form";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
|
||||
const AudioDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4 bg-slate-100">
|
||||
<FormAudioDetail />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -77,12 +77,21 @@ const useTableColumns = () => {
|
|||
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",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorName") || row.getValue("createdByName")}
|
||||
{row.original.creatorName || row.original.createdByName || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ import useTableColumns from "./columns";
|
|||
const TableTeks = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
|
|
@ -76,18 +75,14 @@ const TableTeks = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [showData, setShowData] = React.useState("10");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
|
|
@ -99,7 +94,6 @@ const TableTeks = () => {
|
|||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
|
|
@ -179,13 +173,12 @@ const TableTeks = () => {
|
|||
: "";
|
||||
|
||||
try {
|
||||
// Using the new interface-based approach for video content
|
||||
const filters: ArticleFilters = {
|
||||
page: page,
|
||||
page,
|
||||
totalPage: Number(showData),
|
||||
title: search || undefined,
|
||||
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
|
||||
typeId: 3,
|
||||
typeId: 3, // 🔹 text content
|
||||
statusId:
|
||||
statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
|
||||
startDate: formattedStartDate || undefined,
|
||||
|
|
@ -194,14 +187,17 @@ const TableTeks = () => {
|
|||
|
||||
const res = await listArticlesWithFilters(filters);
|
||||
const data = res?.data?.data;
|
||||
const meta = res?.data?.meta;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
setDataTable(data);
|
||||
setTotalData(data.length);
|
||||
setTotalPage(Math.ceil(data.length / Number(showData)));
|
||||
setTotalData(meta?.count ?? data.length);
|
||||
setTotalPage(
|
||||
meta?.totalPage ?? Math.ceil(data.length / Number(showData))
|
||||
);
|
||||
} else if (Array.isArray(data?.content)) {
|
||||
const contentData = data.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
|
|
@ -218,7 +214,7 @@ const TableTeks = () => {
|
|||
setTotalPage(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
console.error("Error fetching text content:", error);
|
||||
setDataTable([]);
|
||||
setTotalData(0);
|
||||
setTotalPage(1);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import FormTeks from "@/components/form/content/document/teks-form";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
|
||||
const TeksCreatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<div className="space-y-4">
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4 bg-slate-100">
|
||||
<FormTeks />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import FormTeksDetail from "@/components/form/content/document/teks-detail-form";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
|
||||
const TeksDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<div className="space-y-4">
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4 bg-slate-100">
|
||||
<FormTeksDetail />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
getArticleDetail,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
publishMedia,
|
||||
submitApproval,
|
||||
} from "@/service/content/content";
|
||||
|
|
@ -47,7 +45,6 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import SuggestionModal from "@/components/modal/suggestions-modal";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||
import { listArticleCategories } from "@/service/content";
|
||||
|
|
@ -62,19 +59,13 @@ const ViewEditor = dynamic(() => import("@/components/editor/view-editor"), {
|
|||
ssr: false,
|
||||
});
|
||||
|
||||
type Category = {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type Category = { id: string; title: string };
|
||||
type FileType = {
|
||||
id: number;
|
||||
fileName: string;
|
||||
fileUrl?: string;
|
||||
fileThumbnail?: string;
|
||||
format?: string;
|
||||
};
|
||||
|
||||
type Detail = {
|
||||
id: number;
|
||||
title: string;
|
||||
|
|
@ -137,46 +128,28 @@ export default function FormVideoDetail() {
|
|||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const { id } = useParams() as { id: string };
|
||||
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const userLevelName = Cookies.get("state");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const [detail, setDetail] = useState<Detail | null>(null);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [approval, setApproval] = useState<any>();
|
||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
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 {
|
||||
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";
|
||||
};
|
||||
const { control } = useForm({ resolver: zodResolver(videoSchema) });
|
||||
|
||||
useEffect(() => {
|
||||
if (userLevelId == "216" && roleId == "3") {
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
if (userLevelId == "216" && roleId == "3") setIsUserMabesApprover(true);
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -193,29 +166,16 @@ export default function FormVideoDetail() {
|
|||
try {
|
||||
const response = await getArticleDetail(Number(id));
|
||||
const details = response?.data?.data;
|
||||
setSelectedCategory(String(details.categories[0].id));
|
||||
const mappedDetail: Detail = {
|
||||
...details,
|
||||
statusId: details?.statusId,
|
||||
categoryId: details?.categories?.[0]?.id,
|
||||
htmlDescription: details?.htmlDescription,
|
||||
statusName: getStatusName(details?.statusId),
|
||||
uploadedById: details?.createdById,
|
||||
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);
|
||||
setFiles(mappedFiles);
|
||||
setDetailVideos(mappedFiles.map((f: any) => f.fileUrl || ""));
|
||||
setFiles(details?.files || []);
|
||||
const approvals = await getDataApprovalByMediaUpload(mappedDetail.id);
|
||||
setApproval(approvals?.data?.data);
|
||||
} catch (err) {
|
||||
|
|
@ -223,33 +183,32 @@ export default function FormVideoDetail() {
|
|||
}
|
||||
}
|
||||
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) => {
|
||||
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 temp = [];
|
||||
for (const element of files) temp.push([]);
|
||||
setFilePlacements(temp);
|
||||
setStatus(e);
|
||||
const actionApproval = (type: string) => {
|
||||
const placements = files.map(() => []);
|
||||
setFilePlacements(placements);
|
||||
setStatus(type);
|
||||
setDescription("");
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (
|
||||
(description?.length > 1 && Number(status) == 3) ||
|
||||
Number(status) == 2 ||
|
||||
Number(status) == 4
|
||||
) {
|
||||
await save();
|
||||
}
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
const data = {
|
||||
action: status == "2" ? "approve" : status == "3" ? "revision" : "reject",
|
||||
|
|
@ -257,8 +216,8 @@ export default function FormVideoDetail() {
|
|||
};
|
||||
setModalOpen(false);
|
||||
loading();
|
||||
const response = await submitApproval(id, data);
|
||||
if (response?.error) return error(response.message);
|
||||
const res = await submitApproval(id, data);
|
||||
if (res?.error) return error(res.message);
|
||||
close();
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -272,23 +231,15 @@ export default function FormVideoDetail() {
|
|||
successCallback();
|
||||
};
|
||||
|
||||
if (!detail) {
|
||||
return (
|
||||
<div className="text-center py-20 text-gray-500">
|
||||
Memuat detail video...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!detail) return <div className="p-10 text-gray-500">Memuat data...</div>;
|
||||
|
||||
return (
|
||||
<form>
|
||||
<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">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Video</p>
|
||||
|
||||
{/* Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Title</Label>
|
||||
<Controller
|
||||
|
|
@ -296,31 +247,22 @@ export default function FormVideoDetail() {
|
|||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="text"
|
||||
value={detail?.title}
|
||||
value={detail.title}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{/* {errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
value={selectedCategory}
|
||||
onValueChange={setSelectedCategory}
|
||||
disabled
|
||||
>
|
||||
<Select disabled value={String(detail.categoryId)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih kategori" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories?.map((cat) => (
|
||||
{categories.map((cat) => (
|
||||
<SelectItem key={cat.id} value={String(cat.id)}>
|
||||
{cat.title}
|
||||
</SelectItem>
|
||||
|
|
@ -328,29 +270,12 @@ export default function FormVideoDetail() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</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">
|
||||
<Label>Description</Label>
|
||||
<ViewEditor initialData={detail.htmlDescription} />
|
||||
</div>
|
||||
|
||||
{/* Video Files */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xl">File Media</Label>
|
||||
<Swiper
|
||||
|
|
@ -391,24 +316,19 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
</Card>
|
||||
|
||||
{/* RIGHT SIDE */}
|
||||
<div className="w-full lg:w-4/12 m-2">
|
||||
<Card className="pb-3">
|
||||
<div className="px-3 py-3">
|
||||
<Label>Creator</Label>
|
||||
<Input
|
||||
value={detail.createdByName}
|
||||
disabled
|
||||
placeholder="Creator"
|
||||
/>
|
||||
<Input value={detail.createdByName} disabled />
|
||||
</div>
|
||||
|
||||
<div className="mt-3 px-3 space-y-2">
|
||||
<Label>Preview</Label>
|
||||
<Card className="mt-2 w-fit">
|
||||
<img
|
||||
src={detail.thumbnailUrl}
|
||||
alt="Thumbnail"
|
||||
src={detail.thumbnailUrl || detail.thumbnailLink}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className="h-[200px] rounded"
|
||||
/>
|
||||
</Card>
|
||||
|
|
@ -417,10 +337,10 @@ export default function FormVideoDetail() {
|
|||
<div className="px-3 py-3">
|
||||
<Label>Tags</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail.tags?.split(",").map((tag: string, index: number) => (
|
||||
{detail.tags?.split(",").map((tag, i) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md bg-black text-white px-2 py-2"
|
||||
key={i}
|
||||
className="border bg-black text-white px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
|
|
@ -436,24 +356,18 @@ export default function FormVideoDetail() {
|
|||
id={String(target)}
|
||||
checked={selectedPublishers.includes(target)}
|
||||
onChange={() => handleCheckboxChange(target)}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor={String(target)}>
|
||||
{target === 5 ? "UMUM" : target === 6 ? "JOURNALIS" : ""}
|
||||
{target === 5 ? "UMUM" : "JOURNALIS"}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* <SuggestionModal
|
||||
id={Number(id)}
|
||||
numberOfSuggestion={detail?.numberOfSuggestion}
|
||||
/> */}
|
||||
|
||||
<div className="px-3 py-3 border mx-3">
|
||||
<p>Information:</p>
|
||||
<p className="text-sm text-slate-400">
|
||||
{detail.statusName || getStatusName(detail.statusId)}
|
||||
{detail.statusId && getStatusName(detail.statusId)}
|
||||
</p>
|
||||
<p>Komentar</p>
|
||||
<p>{approval?.message}</p>
|
||||
|
|
@ -471,6 +385,108 @@ export default function FormVideoDetail() {
|
|||
</Button>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -256,8 +256,11 @@ export default function FormAudioDetail() {
|
|||
|
||||
setDetail(details);
|
||||
setMain({
|
||||
type: details?.fileType.name,
|
||||
url: details?.files[0]?.url,
|
||||
type:
|
||||
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,
|
||||
format: details?.files[0]?.format,
|
||||
});
|
||||
|
|
@ -448,8 +451,8 @@ export default function FormAudioDetail() {
|
|||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
|
||||
<Card className="w-full lg:w-8/12 m-2">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Audio</p>
|
||||
<div className="gap-5 mb-5">
|
||||
|
|
@ -533,31 +536,12 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-full lg:w-4/12">
|
||||
<div className="w-full lg:w-4/12 m-2">
|
||||
<Card className="pb-3">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Creator</Label>
|
||||
<Controller
|
||||
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>
|
||||
)}
|
||||
<Input value={detail.createdByName} disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tags</Label>
|
||||
|
|
@ -567,7 +551,7 @@ export default function FormAudioDetail() {
|
|||
.map((tag: string, index: number) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="border rounded-md bg-black text-white px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
|
|
@ -576,40 +560,26 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
id="4"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</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>
|
||||
<SuggestionModal
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ import FileTextPreview from "../file-preview-text";
|
|||
import FileTextThumbnail from "../file-text-thumbnail";
|
||||
import { listArticleCategories } from "@/service/content";
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z
|
||||
|
|
@ -120,13 +125,11 @@ export default function FormTeksDetail() {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const { id } = useParams() as { id: string };
|
||||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type ImageSchema = z.infer<typeof imageSchema>;
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -146,11 +149,16 @@ export default function FormTeksDetail() {
|
|||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isMabesApprover, setIsMabesApprover] = useState(false);
|
||||
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
|
||||
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";
|
||||
|
||||
|
|
@ -218,43 +226,63 @@ export default function FormTeksDetail() {
|
|||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const response = await getArticleDetail(Number(id));
|
||||
const details = response?.data?.data;
|
||||
console.log("detail", details);
|
||||
setSelectedCategory(String(details.categories[0].id));
|
||||
if (!details) return;
|
||||
|
||||
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);
|
||||
|
||||
// ✅ Aman untuk fileType
|
||||
setMain({
|
||||
type: details?.fileType.name,
|
||||
url: details?.files[0]?.url,
|
||||
names: details?.files[0]?.fileName,
|
||||
format: details?.files[0]?.format,
|
||||
type: details?.fileType?.name || "Unknown",
|
||||
url: details?.files?.[0]?.url || "",
|
||||
names: details?.files?.[0]?.fileName || "",
|
||||
format: details?.files?.[0]?.format || "",
|
||||
});
|
||||
|
||||
if (details.publishedForObject) {
|
||||
if (details?.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
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);
|
||||
|
||||
const approvals = await getDataApprovalByMediaUpload(details?.id);
|
||||
setApproval(approvals?.data?.data);
|
||||
} catch (err) {
|
||||
console.error("Error loading article details:", err);
|
||||
}
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
|
|
@ -392,15 +420,15 @@ export default function FormTeksDetail() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/admin/content/document");
|
||||
router.push("/admin/content/text");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
|
||||
<Card className="w-full lg:w-8/12 m-2">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Text</p>
|
||||
<div className="gap-5 mb-5">
|
||||
|
|
@ -498,29 +526,11 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-full lg:w-4/12">
|
||||
<div className="w-full lg:w-4/12 m-2">
|
||||
<Card className="pb-3">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Creator</Label>
|
||||
<Controller
|
||||
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>
|
||||
<Input value={detail.createdByName} disabled />
|
||||
</div>
|
||||
{/* <div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
|
|
@ -541,7 +551,7 @@ export default function FormTeksDetail() {
|
|||
.map((tag: string, index: number) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="border rounded-md bg-black text-white px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
|
|
@ -550,40 +560,26 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
id="4"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</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>
|
||||
<SuggestionModal
|
||||
|
|
@ -821,10 +817,10 @@ export default function FormTeksDetail() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
Number(detail?.uploadedById) == Number(userId) ? (
|
||||
""
|
||||
) : (
|
||||
) : ( */}
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
|
|
@ -849,10 +845,10 @@ export default function FormTeksDetail() {
|
|||
Reject
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
{/* )
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -144,10 +144,8 @@ export default function FormTeks() {
|
|||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
{ id: "5", label: "UMUM" },
|
||||
{ id: "6", label: "JOURNALIS" },
|
||||
{ id: "7", label: "POLRI" },
|
||||
{ id: "8", label: "KSP" },
|
||||
{ id: "4", label: "UMUM" },
|
||||
{ id: "5", label: "JOURNALIS" },
|
||||
];
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
|
|
@ -638,12 +636,11 @@ export default function FormTeks() {
|
|||
}
|
||||
|
||||
if (id == undefined) {
|
||||
// New Articles API request data structure
|
||||
const articleData: CreateArticleData = {
|
||||
aiArticleId: 0, // default 0
|
||||
aiArticleId: 0,
|
||||
categoryIds: selectedCategory.toString(),
|
||||
createdAt: formatDateForBackend(new Date()), // ✅ format sesuai backend
|
||||
createdById: Number(userId), // isi dengan userId valid
|
||||
createdAt: formatDateForBackend(new Date()),
|
||||
createdById: Number(userId),
|
||||
description: htmlToString(finalDescription),
|
||||
htmlDescription: finalDescription,
|
||||
isDraft: true,
|
||||
|
|
@ -655,9 +652,8 @@ export default function FormTeks() {
|
|||
.replace(/[^a-z0-9-]/g, ""),
|
||||
tags: finalTags,
|
||||
title: finalTitle,
|
||||
typeId: 1, // Image content type
|
||||
typeId: 3,
|
||||
};
|
||||
// Use new Articles API
|
||||
const response = await createArticle(articleData);
|
||||
console.log("Article Data Submitted:", articleData);
|
||||
console.log("Article API Response:", response);
|
||||
|
|
@ -670,16 +666,12 @@ export default function FormTeks() {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the article ID from the new API response
|
||||
const articleId = response?.data?.data?.id;
|
||||
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||
id = articleId;
|
||||
|
||||
// Upload files using new article-files API
|
||||
const formData = new FormData();
|
||||
|
||||
// Add all files to FormData
|
||||
files.forEach((file, index) => {
|
||||
formData.append("files", file);
|
||||
});
|
||||
|
|
@ -701,10 +693,9 @@ export default function FormTeks() {
|
|||
|
||||
console.log("Files uploaded successfully:", uploadResponse);
|
||||
|
||||
// Upload thumbnail using first file as thumbnail
|
||||
if (files.length > 0) {
|
||||
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);
|
||||
|
||||
|
|
@ -719,7 +710,6 @@ export default function FormTeks() {
|
|||
"Thumbnail upload failed:",
|
||||
thumbnailResponse.message
|
||||
);
|
||||
// Don't fail the whole process if thumbnail upload fails
|
||||
} else {
|
||||
console.log(
|
||||
"Thumbnail uploaded successfully:",
|
||||
|
|
@ -728,7 +718,6 @@ export default function FormTeks() {
|
|||
}
|
||||
} catch (thumbnailError) {
|
||||
console.warn("Thumbnail upload error:", thumbnailError);
|
||||
// Don't fail the whole process if thumbnail upload fails
|
||||
}
|
||||
}
|
||||
} catch (uploadError) {
|
||||
|
|
@ -741,7 +730,6 @@ export default function FormTeks() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Article dan files berhasil disimpan.",
|
||||
|
|
@ -749,7 +737,7 @@ export default function FormTeks() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/admin/content/document");
|
||||
router.push("/admin/content/text");
|
||||
});
|
||||
|
||||
Cookies.remove("idCreate");
|
||||
|
|
@ -980,8 +968,8 @@ export default function FormTeks() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
|
||||
<Card className="w-full lg:w-8/12 m-2">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Document</p>
|
||||
<div className="gap-5 mb-5">
|
||||
|
|
@ -1428,7 +1416,7 @@ export default function FormTeks() {
|
|||
{/* Submit Button */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-full lg:w-4/12">
|
||||
<div className="w-full lg:w-4/12 m-2">
|
||||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -1561,6 +1549,7 @@ export default function FormTeks() {
|
|||
id={option.id}
|
||||
checked={isChecked}
|
||||
onCheckedChange={handleChange}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.label}</Label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import React from "react";
|
|||
|
||||
type FileData = {
|
||||
url: string;
|
||||
format: string; // extension with dot, e.g. ".pdf"
|
||||
format: string; // bisa ".pdf" atau "mp4" atau "image/jpeg"
|
||||
fileName?: string;
|
||||
type?: string; // optional dari API (misal: video/mp4)
|
||||
};
|
||||
|
||||
interface FilePreviewProps {
|
||||
|
|
@ -11,48 +12,102 @@ interface FilePreviewProps {
|
|||
}
|
||||
|
||||
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 (
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
className="object-contain h-[500px] w-full rounded-md bg-black"
|
||||
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 (
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(file.url)}`}
|
||||
className="w-full h-[600px] rounded-md"
|
||||
src={`https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(
|
||||
file.url
|
||||
)}`}
|
||||
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 (
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(file.url)}`}
|
||||
title={file.fileName || "Document"}
|
||||
className="w-full h-[600px] rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
file.url
|
||||
)}`}
|
||||
title={file.fileName || "Office Document"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback → unknown format
|
||||
// 🔗 Default fallback → link download
|
||||
return (
|
||||
<div className="text-center py-20">
|
||||
<a
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-blue-500 underline"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
View {file.fileName || "File"}
|
||||
Lihat {file.fileName || "File"}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -117,32 +117,52 @@ export default function MediaUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
// Function to filter data by content type
|
||||
function filterDataByContentType() {
|
||||
// if (contentType === "all") {
|
||||
// setFilteredData(dataToRender);
|
||||
// return;
|
||||
// }
|
||||
|
||||
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;
|
||||
// Function to filter data by content type
|
||||
|
||||
// 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) {
|
||||
case "audiovisual":
|
||||
return hasVideo && (hasAudio || hasImage);
|
||||
return item.typeId === 2; // Video
|
||||
case "audio":
|
||||
return hasAudio && !hasVideo;
|
||||
return item.typeId === 4; // Audio
|
||||
case "foto":
|
||||
return hasImage && !hasVideo && !hasAudio;
|
||||
return item.typeId === 1; // Image
|
||||
case "text":
|
||||
return hasText && !hasVideo && !hasAudio && !hasImage;
|
||||
return item.typeId === 3; // Text
|
||||
default:
|
||||
return true;
|
||||
return true; // Semua jenis
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue