From 4f02c4ae18eddd5be644a49dfa998e35ced58380 Mon Sep 17 00:00:00 2001 From: Sabda Yagra Date: Tue, 23 Sep 2025 08:35:49 +0700 Subject: [PATCH] fixing sabda --- .../content/image/components/columns.tsx | 90 +--- .../content/image/components/table-image.tsx | 88 +++- .../form/content/image/image-detail-form.tsx | 103 ++++- components/form/content/image/image-form.tsx | 178 +++----- .../landing-page/retracting-sidedar.tsx | 429 +++++++++--------- service/content/content.ts | 37 +- service/curated-content/curated-content.ts | 2 +- .../http-config/http-interceptor-service.ts | 50 +- 8 files changed, 521 insertions(+), 456 deletions(-) diff --git a/app/(admin)/admin/content/image/components/columns.tsx b/app/(admin)/admin/content/image/components/columns.tsx index ca690a1..af11de1 100644 --- a/app/(admin)/admin/content/image/components/columns.tsx +++ b/app/(admin)/admin/content/image/components/columns.tsx @@ -15,7 +15,7 @@ import { Badge } from "@/components/ui/badge"; import { format } from "date-fns"; import { useRouter } from "next/navigation"; import { deleteMedia } from "@/service/content/content"; -import { error, loading } from "@/lib/swal"; +import { error } from "@/lib/swal"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; import Link from "next/link"; @@ -41,7 +41,7 @@ const useTableColumns = () => { { accessorKey: "title", header: "Title", - cell: ({ row }: { row: { getValue: (key: string) => string } }) => { + cell: ({ row }) => { const title: string = row.getValue("title"); return ( @@ -56,24 +56,17 @@ const useTableColumns = () => { cell: ({ row }) => { const categoryName = row.getValue("categoryName"); const categories = row.original.categories; - // Handle new API structure with categories array - const displayName = categoryName || (categories && categories.length > 0 ? categories[0].title : "-"); - return ( - - {displayName} - - ); + const displayName = + categoryName || + (categories && categories.length > 0 ? categories[0].title : "-"); + return {displayName}; }, }, { accessorKey: "createdAt", header: "Upload Date", cell: ({ row }) => { - const createdAt = row.getValue("createdAt") as - | string - | number - | undefined; - + const createdAt = row.getValue("createdAt") as string | number | undefined; const formattedDate = createdAt && !isNaN(new Date(createdAt).getTime()) ? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss") @@ -86,7 +79,7 @@ const useTableColumns = () => { header: "Creator Group", cell: ({ row }) => ( - {row.getValue("creatorName") || row.getValue("createdByName")} + {row.original.creatorName || row.original.createdByName || "-"} ), }, @@ -105,20 +98,19 @@ const useTableColumns = () => { cell: ({ row }) => { const isPublish = row.original.isPublish; const isPublishOnPolda = row.original.isPublishOnPolda; - const creatorGroupParentLevelId = - row.original.creatorGroupParentLevelId; + const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId; let displayText = "-"; if (isPublish && !isPublishOnPolda) { displayText = "Mabes"; } else if (isPublish && isPublishOnPolda) { - if (Number(creatorGroupParentLevelId) == 761) { + if (Number(creatorGroupParentLevelId) === 761) { displayText = "Mabes & Satker"; } else { displayText = "Mabes & Polda"; } } else if (!isPublish && isPublishOnPolda) { - if (Number(creatorGroupParentLevelId) == 761) { + if (Number(creatorGroupParentLevelId) === 761) { displayText = "Satker"; } else { displayText = "Polda"; @@ -132,7 +124,6 @@ const useTableColumns = () => { ); }, }, - // { accessorKey: "statusName", header: "Status", @@ -140,18 +131,13 @@ const useTableColumns = () => { const statusId = Number(row.original?.statusId); const reviewedAtLevel = row.original?.reviewedAtLevel || ""; const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId); - const needApprovalFromLevel = Number( - row.original?.needApprovalFromLevel - ); + const needApprovalFromLevel = Number(row.original?.needApprovalFromLevel); const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`); const isCreator = creatorGroupLevelId === Number(userLevelId); - const isWaitingForReview = - statusId === 2 && !userHasReviewed && !isCreator; - - const isApprovalNeeded = - statusId === 1 && needApprovalFromLevel === Number(userLevelId); + const isWaitingForReview = statusId === 2 && !userHasReviewed && !isCreator; + const isApprovalNeeded = statusId === 1 && needApprovalFromLevel === Number(userLevelId); const label = isWaitingForReview || isApprovalNeeded @@ -169,18 +155,12 @@ const useTableColumns = () => { const statusStyles = colors[label] || colors.default; return ( - + {label} ); }, }, - { id: "actions", accessorKey: "action", @@ -191,13 +171,8 @@ const useTableColumns = () => { const MySwal = withReactContent(Swal); async function doDelete(id: any) { - // loading(); - const data = { - id, - }; - + const data = { id }; const response = await deleteMedia(data); - if (response?.error) { error(response.message); return false; @@ -221,7 +196,6 @@ const useTableColumns = () => { const handleDeleteMedia = (id: any) => { MySwal.fire({ title: "Hapus Data", - text: "", icon: "warning", showCancelButton: true, cancelButtonColor: "#3085d6", @@ -241,9 +215,7 @@ const useTableColumns = () => { React.useEffect(() => { if (userLevelId !== undefined && roleId !== undefined) { - setIsMabesApprover( - Number(userLevelId) == 216 && Number(roleId) == 3 - ); + setIsMabesApprover(Number(userLevelId) === 216 && Number(roleId) === 3); } }, [userLevelId, roleId]); @@ -263,23 +235,14 @@ const useTableColumns = () => { href={`/admin/content/image/detail/${row.original.id}`} className="hover:text-black" > - + View - {/* - - - Edit - - */} - {(Number(row.original.uploadedById) === Number(userId) || - isMabesApprover) && ( + {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( - + Edit @@ -287,20 +250,11 @@ const useTableColumns = () => { )} handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" + className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" > - + Delete - {/* {(row.original.uploadedById === userId || isMabesApprover) && ( - handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none" - > - - Hapus - - )} */} ); diff --git a/app/(admin)/admin/content/image/components/table-image.tsx b/app/(admin)/admin/content/image/components/table-image.tsx index 5fd7a56..ed2dad9 100644 --- a/app/(admin)/admin/content/image/components/table-image.tsx +++ b/app/(admin)/admin/content/image/components/table-image.tsx @@ -178,6 +178,51 @@ const TableImage = () => { }); }; + // async function fetchData() { + // const formattedStartDate = startDate + // ? format(new Date(startDate), "yyyy-MM-dd") + // : ""; + // const formattedEndDate = endDate + // ? format(new Date(endDate), "yyyy-MM-dd") + // : ""; + // try { + // // Using the new interface-based approach for image content + // const filters: ArticleFilters = { + // page: page, + // totalPage: Number(showData), + // title: search || undefined, + // categoryId: categoryFilter ? Number(categoryFilter) : undefined, + // typeId: 1, // image content type + // statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, + // startDate: formattedStartDate || undefined, + // endDate: formattedEndDate || undefined, + // }; + + // const res = await listArticlesWithFilters(filters); + + // const data = res?.data?.data; + // // Handle new articles API response structure + // 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))); + // } else { + // // Fallback to old structure if API still returns old format + // const contentData = data?.content; + // contentData.forEach((item: any, index: number) => { + // item.no = (page - 1) * Number(showData) + index + 1; + // }); + // setDataTable(contentData); + // setTotalData(data?.totalElements); + // setTotalPage(data?.totalPages); + // } + // } catch (error) { + // console.error("Error fetching tasks:", error); + // } + // } async function fetchData() { const formattedStartDate = startDate ? format(new Date(startDate), "yyyy-MM-dd") @@ -185,42 +230,47 @@ const TableImage = () => { const formattedEndDate = endDate ? format(new Date(endDate), "yyyy-MM-dd") : ""; + try { - // Using the new interface-based approach for image content const filters: ArticleFilters = { - page: page, + page, totalPage: Number(showData), title: search || undefined, categoryId: categoryFilter ? Number(categoryFilter) : undefined, typeId: 1, // image content type - statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, + statusId: + statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, startDate: formattedStartDate || undefined, endDate: formattedEndDate || undefined, }; const res = await listArticlesWithFilters(filters); - const data = res?.data?.data; - // Handle new articles API response structure + if (Array.isArray(data)) { - data.forEach((item: any, index: number) => { - item.no = (page - 1) * Number(showData) + index + 1; - }); - setDataTable(data); + // ✅ aman karena data array + const processed = data.map((item: any, index: number) => ({ + ...item, + no: (page - 1) * Number(showData) + index + 1, + })); + setDataTable(processed); setTotalData(data.length); setTotalPage(Math.ceil(data.length / Number(showData))); } else { - // Fallback to old structure if API still returns old format - const contentData = data?.content; - contentData.forEach((item: any, index: number) => { - item.no = (page - 1) * Number(showData) + index + 1; - }); - setDataTable(contentData); - setTotalData(data?.totalElements); - setTotalPage(data?.totalPages); + // ✅ fallback kalau masih pakai struktur lama + const contentData = Array.isArray(data?.content) ? data.content : []; + const processed = contentData.map((item: any, index: number) => ({ + ...item, + no: (page - 1) * Number(showData) + index + 1, + })); + + setDataTable(processed); + setTotalData(data?.totalElements ?? 0); + setTotalPage(data?.totalPages ?? 1); } - } catch (error) { - console.error("Error fetching tasks:", error); + } catch (err) { + console.error("Error fetching tasks:", err); + setDataTable([]); // fallback aman } } diff --git a/components/form/content/image/image-detail-form.tsx b/components/form/content/image/image-detail-form.tsx index 82560b4..023fe7a 100644 --- a/components/form/content/image/image-detail-form.tsx +++ b/components/form/content/image/image-detail-form.tsx @@ -197,7 +197,7 @@ export default function FormImageDetail() { const getCategories = async () => { try { const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; + const resCategory: Category[] = category?.data; setCategories(resCategory); console.log("data category", resCategory); @@ -211,7 +211,7 @@ export default function FormImageDetail() { // setValue("categoryId", findCategory.id); setSelectedCategory(findCategory.id); // Set the selected category const response = await getTagsBySubCategoryId(findCategory.id); - setTags(response?.data?.data); + setTags(response?.data); } } } catch (error) { @@ -227,40 +227,95 @@ export default function FormImageDetail() { setFilePlacements(temp); }; + // useEffect(() => { + // async function initState() { + // if (id) { + // const response = await detailMedia(id); + // const details = response?.data; + // console.log("detail", details); + // setFiles(details?.files); + // setDetail(details); + // setMain({ + // type: details?.fileType.name, + // url: details?.files[0]?.url, + // names: details?.files[0]?.fileName, + // format: details?.files[0]?.format, + // }); + // setupPlacementCheck(details?.files?.length); + + // 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)); + + // const filesData = details.files || []; + // const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) => + // file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg" + // ); + // setDetailThumb(fileUrls); + + // const approvals = await getDataApprovalByMediaUpload(details?.id); + // setApproval(approvals?.data); + // } + // } + // initState(); + // }, [refresh, setValue]); + useEffect(() => { async function initState() { if (id) { const response = await detailMedia(id); - const details = response?.data?.data; + const details = response?.data; console.log("detail", details); - setFiles(details?.files); - setDetail(details); - setMain({ - type: details?.fileType.name, - url: details?.files[0]?.url, - names: details?.files[0]?.fileName, - format: details?.files[0]?.format, - }); - setupPlacementCheck(details?.files?.length); - if (details.publishedForObject) { + // Set detail untuk ditampilkan + setDetail(details); + + // Files + setFiles(details?.files); + + // Ambil file pertama sebagai "main" + if (details?.files && details.files.length > 0) { + setMain({ + type: "image", // atau mapping sendiri kalau ada typeId + url: details.files[0].file_url, + names: details.files[0].file_name, + format: details.files[0].file_name.split(".").pop(), // ambil ekstensi + }); + } + + setupPlacementCheck(details?.files?.length ?? 0); + + // Kalau ada 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)); + // Set target category + if (details?.categories && details.categories.length > 0) { + setSelectedTarget(String(details.categories[0].id)); + } else if (details?.categoryId) { + setSelectedTarget(String(details.categoryId)); + } - const filesData = details.files || []; - const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) => - file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg" + // Thumbnails + const filesData = details?.files || []; + const fileUrls = filesData.map((file: any) => + file.file_thumbnail ? file.file_thumbnail : file.file_url ); setDetailThumb(fileUrls); + // Ambil approval const approvals = await getDataApprovalByMediaUpload(details?.id); - setApproval(approvals?.data?.data); + setApproval(approvals?.data); } } initState(); @@ -490,6 +545,7 @@ export default function FormImageDetail() { {/* Show the category from details if it doesn't exist in categories list */} {detail && + Array.isArray(categories) && !categories.find( (cat) => String(cat.id) === String(detail.category.id) @@ -501,12 +557,13 @@ export default function FormImageDetail() { {detail.category.name} )} - {categories.map((category) => ( + + {categories?.map((cat) => ( - {category.name} + {cat.name} ))} diff --git a/components/form/content/image/image-form.tsx b/components/form/content/image/image-form.tsx index 1bc1d75..d2f90af 100644 --- a/components/form/content/image/image-form.tsx +++ b/components/form/content/image/image-form.tsx @@ -474,24 +474,26 @@ export default function FormImage() { // Use new Article Categories API const category = await listArticleCategories(1, 100); console.log("Article categories response:", category); - + if (category?.error) { console.error("Failed to fetch article categories:", category.message); // Fallback to old API if new one fails const fallbackCategory = await listEnableCategory(fileTypeId); - const resCategory: Category[] = fallbackCategory?.data.data.content || []; + const resCategory: Category[] = + fallbackCategory?.data.data.content || []; setCategories(resCategory); return; } // Handle new API response structure - const resCategory: Category[] = category?.data?.data?.map((item: any) => ({ - id: item.id, - name: item.title, // map title to name for backward compatibility - title: item.title, - description: item.description, - ...item - })) || []; + const resCategory: Category[] = + category?.data?.data?.map((item: any) => ({ + id: item.id, + name: item.title, // map title to name for backward compatibility + title: item.title, + description: item.description, + ...item, + })) || []; setCategories(resCategory); console.log("Article categories loaded:", resCategory); @@ -512,7 +514,8 @@ export default function FormImage() { // Fallback to old API if error occurs try { const fallbackCategory = await listEnableCategory(fileTypeId); - const resCategory: Category[] = fallbackCategory?.data.data.content || []; + const resCategory: Category[] = + fallbackCategory?.data.data.content || []; setCategories(resCategory); } catch (fallbackError) { console.error("Fallback category fetch also failed:", fallbackError); @@ -550,6 +553,8 @@ export default function FormImage() { } }, [articleBody, setValue]); + const userId = Cookies.get("userId"); // atau dari auth context / localStorage + const save = async (data: ImageSchema) => { loading(); @@ -560,7 +565,6 @@ export default function FormImage() { const finalTags = tags.join(", "); const finalTitle = isSwitchOn ? title : data.title; - // const finalDescription = articleBody || data.description; const finalDescription = isSwitchOn ? data.description : selectedFileType === "rewrite" @@ -572,125 +576,94 @@ export default function FormImage() { return; } - // New Articles API request data structure + function formatDateForBackend(date: Date) { + const pad = (n: number) => (n < 10 ? "0" + n : n); + return ( + date.getFullYear() + + "-" + + pad(date.getMonth() + 1) + + "-" + + pad(date.getDate()) + + " " + + pad(date.getHours()) + + ":" + + pad(date.getMinutes()) + + ":" + + pad(date.getSeconds()) + ); + } + + // ✅ Sesuaikan dengan struktur Swagger const articleData: CreateArticleData = { - title: finalTitle, + aiArticleId: 0, // default 0 + categoryIds: selectedCategory.toString(), + createdAt: formatDateForBackend(new Date()), // ✅ format sesuai backend + createdById: Number(userId), // isi dengan userId valid description: htmlToString(finalDescription), htmlDescription: finalDescription, - categoryIds: selectedCategory.toString(), - typeId: 1, // Image content type - tags: finalTags, isDraft: true, isPublish: false, oldId: 0, - slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), - }; - - // Keep old structure for backward compatibility if needed - let requestData: { - title: string; - description: string; - htmlDescription: string; - fileTypeId: string; - categoryId: any; - subCategoryId: any; - uploadedBy: string; - statusId: string; - publishedFor: string; - creatorName: string; - tags: string; - isYoutube: boolean; - isInternationalMedia: boolean; - attachFromScheduleId?: number; - } = { - ...data, - title: finalTitle, - description: htmlToString(finalDescription), - htmlDescription: finalDescription, - fileTypeId, - categoryId: selectedCategory, - subCategoryId: selectedCategory, - uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", - statusId: "1", - publishedFor: publishedFor.join(","), - creatorName: data.creatorName, + slug: finalTitle + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9-]/g, ""), tags: finalTags, - isYoutube: false, - isInternationalMedia: false, + title: finalTitle, + typeId: 1, // Image content type }; let id = Cookies.get("idCreate"); - if (scheduleId !== undefined) { - requestData.attachFromScheduleId = Number(scheduleId); - } - if (id == undefined) { - // Use new Articles API const response = await createArticle(articleData); console.log("Article Data Submitted:", articleData); console.log("Article API Response:", response); if (response?.error) { - MySwal.fire("Error", response.message || "Failed to create article", "error"); + MySwal.fire( + "Error", + response.message || "Failed to create article", + "error" + ); 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 + // Upload files const formData = new FormData(); - - // Add all files to FormData - files.forEach((file, index) => { - formData.append('files', file); - }); + files.forEach((file) => formData.append("files", file)); - console.log("Uploading files to article:", articleId); - console.log("Files to upload:", files.length); - try { const uploadResponse = await uploadArticleFiles(articleId, formData); - if (uploadResponse?.error) { - MySwal.fire("Error", uploadResponse.message || "Failed to upload files", "error"); + MySwal.fire( + "Error", + uploadResponse.message || "Failed to upload files", + "error" + ); return false; } - console.log("Files uploaded successfully:", uploadResponse); - - // Upload thumbnail using first file as thumbnail + // Upload thumbnail pakai file pertama if (files.length > 0) { const thumbnailFormData = new FormData(); - thumbnailFormData.append('files', files[0]); // Use first file as thumbnail - - console.log("Uploading thumbnail for article:", articleId); - - try { - const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData); - - if (thumbnailResponse?.error) { - console.warn("Thumbnail upload failed:", thumbnailResponse.message); - // Don't fail the whole process if thumbnail upload fails - } else { - console.log("Thumbnail uploaded successfully:", thumbnailResponse); - } - } catch (thumbnailError) { - console.warn("Thumbnail upload error:", thumbnailError); - // Don't fail the whole process if thumbnail upload fails - } + thumbnailFormData.append("files", files[0]); + await uploadArticleThumbnail(articleId, thumbnailFormData); } - } catch (uploadError) { console.error("Upload error:", uploadError); - MySwal.fire("Error", "Failed to upload files. Please try again.", "error"); + MySwal.fire( + "Error", + "Failed to upload files. Please try again.", + "error" + ); return false; } - - // Show success message + MySwal.fire({ title: "Sukses", text: "Article dan files berhasil disimpan.", @@ -700,28 +673,10 @@ export default function FormImage() { }).then(() => { router.push("/admin/content/image"); }); - + Cookies.remove("idCreate"); return; } - - // Keep old upload logic for backward compatibility - // const progressInfoArr = files.map((item) => ({ - // percentage: 0, - // fileName: item.name, - // })); - // progressInfo = progressInfoArr; - // setIsStartUpload(true); - // setProgressList(progressInfoArr); - - // files.map(async (item: any, index: number) => { - // await uploadResumableFile( - // index, - // String(id), - // item, - // fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" - // ); - // }); Cookies.remove("idCreate"); }; @@ -755,13 +710,12 @@ export default function FormImage() { const resCsrf = await getCsrfToken(); const csrfToken = resCsrf?.data?.token; - console.log("CSRF TOKEN : ", csrfToken); const headers = { "X-XSRF-TOKEN": csrfToken, }; const upload = new Upload(file, { - endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, + endpoint: `${process.env.NEXT_PUBLIC_API}/articles/file/upload`, headers: headers, retryDelays: [0, 3000, 6000, 12_000, 24_000], chunkSize: 20_000, diff --git a/components/landing-page/retracting-sidedar.tsx b/components/landing-page/retracting-sidedar.tsx index b74635c..1900021 100644 --- a/components/landing-page/retracting-sidedar.tsx +++ b/components/landing-page/retracting-sidedar.tsx @@ -1,6 +1,12 @@ "use client"; -import React, { Dispatch, SetStateAction, useState, useEffect } from "react"; +import React, { + Dispatch, + SetStateAction, + useState, + useEffect, + ReactNode, +} from "react"; import Image from "next/image"; import { Icon } from "@iconify/react"; @@ -15,8 +21,27 @@ interface RetractingSidebarProps { sidebarData: boolean; updateSidebarData: (newData: boolean) => void; } +interface SidebarItemBase { + title: string; + icon: () => ReactNode; +} -const sidebarSections = [ +interface SidebarLinkItem extends SidebarItemBase { + link: string; +} + +interface SidebarParentItem extends SidebarItemBase { + children: SidebarLinkItem[]; +} + +type SidebarItem = SidebarLinkItem | SidebarParentItem; + +interface SidebarSection { + title: string; + items: SidebarItem[]; +} + +const sidebarSections: SidebarSection[] = [ { title: "Dashboard", items: [ @@ -33,29 +58,37 @@ const sidebarSections = [ title: "Content", items: [ { - title: "Foto", - icon: () => , - link: "/admin/content/image", - }, - { - title: "Audio Visual", - icon: () => , - link: "/admin/content/audio-visual", - }, - { - title: "Teks", - icon: () => , - link: "/admin/content/document", - }, - { - title: "Audio", - icon: () => ( - - ), - link: "/admin/content/audio", + title: "Master Data", + icon: () => , + children: [ + { + title: "Foto", + icon: () => , + link: "/admin/content/image", + }, + { + title: "Audio Visual", + icon: () => ( + + ), + link: "/admin/content/audio-visual", + }, + { + title: "Teks", + icon: () => , + link: "/admin/content/document", + }, + { + title: "Audio", + icon: () => ( + + ), + link: "/admin/content/audio", + }, + ], }, ], }, @@ -174,6 +207,12 @@ const SidebarContent = ({ updateSidebarData: (newData: boolean) => void; }) => { const { theme, toggleTheme } = useTheme(); + const [expanded, setExpanded] = useState(null); // track parent yang dibuka + + const toggleExpand = (title: string) => { + setExpanded((prev) => (prev === title ? null : title)); + }; + const handleLogout = () => { Object.keys(Cookies.get()).forEach((cookieName) => { Cookies.remove(cookieName); @@ -181,13 +220,13 @@ const SidebarContent = ({ window.location.href = "/"; }; + return (
{/* SCROLLABLE TOP SECTION */}
- {/* HEADER SECTION */}
- {/* Logo and Toggle */} + {/* Logo */}
@@ -230,35 +269,85 @@ const SidebarContent = ({ {/* Navigation Sections */}
- {sidebarSections.map((section, sectionIndex) => ( + {sidebarSections.map((section) => ( {open && ( {section.title} )} +
- {section.items.map((item, itemIndex) => ( - -
))} @@ -266,148 +355,20 @@ const SidebarContent = ({
- {/* FIXED BOTTOM SECTION */} -
- {/* Divider */} - {/*
-
-
*/} - - {/* Theme Toggle */} -
- - -
- {theme === "dark" ? ( - - ) : ( - - )} -
-
- - {open && ( - - {theme === "dark" ? "Light Mode" : "Dark Mode"} - - )} -
-
- - {/* Settings */} -
- -
- - {/* User Profile */} - -
-
-
- A -
-
-
- {open && ( - -

- Mabes Polri - Approver -

- -

- Sign out -

- -
- )} -
-
- - {/* Expand Button for Collapsed State */} - {/* {!open && ( - - - - )} */} -
+ {/* ... (BOTTOM SECTION tetap sama) */}
); }; const Sidebar = () => { const [open, setOpen] = useState(true); + const [expanded, setExpanded] = useState(null); // track submenu yg dibuka const pathname = usePathname(); + const toggleExpand = (title: string) => { + setExpanded((prev) => (prev === title ? null : title)); + }; + return ( { className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center" onClick={() => setOpen(true)} > - - - + ▶
)} + {/* Logo + tombol collapse */}
{ className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center" onClick={() => setOpen(false)} > - - - + ◀ )}
-
+ {/* Menu utama */} +
{sidebarSections.map((section) => (
-

{section.title}

- {section.items.map((item) => ( - -
))}
@@ -511,8 +501,8 @@ const Sidebar = () => { active={pathname === "/settings"} open={open} /> - {" "} -
+ +
{

admin-mabes

-

Logout

+

Logout

@@ -535,7 +525,6 @@ const Sidebar = () => { }; export default Sidebar; - const TitleSection = ({ open }: { open: boolean }) => { return (
diff --git a/service/content/content.ts b/service/content/content.ts index 3b2485c..5b361b9 100644 --- a/service/content/content.ts +++ b/service/content/content.ts @@ -26,16 +26,19 @@ export interface ArticleFilters { // Interface for creating new article export interface CreateArticleData { - title: string; + aiArticleId: number; + categoryIds: string; + createdAt: string; + createdById: number; description: string; htmlDescription: string; - categoryIds: string; - typeId: number; - tags: string; isDraft: boolean; isPublish: boolean; oldId: number; slug: string; + tags: string; + title: string; + typeId: number; } // Interface for Article Category @@ -240,7 +243,10 @@ export async function uploadThumbnail(id: any, data: any) { } // New Articles API - Upload Article Files -export async function uploadArticleFiles(articleId: string | number, files: FormData) { +export async function uploadArticleFiles( + articleId: string | number, + files: FormData +) { const url = `article-files/${articleId}`; const headers = { "Content-Type": "multipart/form-data", @@ -249,7 +255,10 @@ export async function uploadArticleFiles(articleId: string | number, files: Form } // New Articles API - Upload Article Thumbnail -export async function uploadArticleThumbnail(articleId: string | number, thumbnail: FormData) { +export async function uploadArticleThumbnail( + articleId: string | number, + thumbnail: FormData +) { const url = `articles/thumbnail/${articleId}`; const headers = { "Content-Type": "multipart/form-data", @@ -258,7 +267,10 @@ export async function uploadArticleThumbnail(articleId: string | number, thumbna } // New Articles API - Get Article Categories -export async function listArticleCategories(page: number = 1, limit: number = 100) { +export async function listArticleCategories( + page: number = 1, + limit: number = 100 +) { const url = `article-categories?page=${page}&limit=${limit}`; return httpGetInterceptor(url); } @@ -336,7 +348,7 @@ export async function listArticles( endDate?: string ) { let url = `articles?page=${page}&totalPage=${totalPage}`; - + // Add optional query parameters based on available filters if (title) url += `&title=${encodeURIComponent(title)}`; if (description) url += `&description=${encodeURIComponent(description)}`; @@ -351,7 +363,7 @@ export async function listArticles( if (isDraft !== undefined) url += `&isDraft=${isDraft}`; if (startDate) url += `&startDate=${startDate}`; if (endDate) url += `&endDate=${endDate}`; - + return await httpGetInterceptor(url); } @@ -394,8 +406,9 @@ export async function listDataTeksNew( ) { // Convert old parameters to new API format const categoryId = categoryFilter ? Number(categoryFilter) : undefined; - const statusId = statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined; - + const statusId = + statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined; + return await listArticles( page + 1, // API expects 1-based page Number(size), @@ -413,4 +426,4 @@ export async function listDataTeksNew( startDate, endDate ); -} \ No newline at end of file +} diff --git a/service/curated-content/curated-content.ts b/service/curated-content/curated-content.ts index 252ab82..658dfc3 100644 --- a/service/curated-content/curated-content.ts +++ b/service/curated-content/curated-content.ts @@ -5,7 +5,7 @@ import { } from "../http-config/http-interceptor-service"; export async function detailMedia(id: any) { - const url = `media?id=${id}`; + const url = `articles?id=${id}`; return httpGetInterceptor(url); } diff --git a/service/http-config/http-interceptor-service.ts b/service/http-config/http-interceptor-service.ts index 108cbc8..f996684 100644 --- a/service/http-config/http-interceptor-service.ts +++ b/service/http-config/http-interceptor-service.ts @@ -59,6 +59,46 @@ export async function httpGetInterceptor(pathUrl: any) { } } +// export async function httpPostInterceptor( +// pathUrl: any, +// data?: any, +// headers?: any +// ) { +// const resCsrf = await getCsrfToken(); +// const csrfToken = resCsrf?.data?.token; + +// const defaultHeaders = { +// "Content-Type": "application/json", +// }; +// const mergedHeaders = { +// ...defaultHeaders, +// ...(csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {}), +// ...headers, +// }; + +// const response = await axiosInterceptorInstance +// .post(pathUrl, data, { headers: mergedHeaders }) +// .catch((error) => error.response); +// console.log("Response interceptor : ", response); +// if (response?.status == 200 || response?.status == 201) { +// return { +// error: false, +// message: "success", +// data: response?.data, +// }; +// } else if (response?.status == 401) { +// Object.keys(Cookies.get()).forEach((cookieName) => { +// Cookies.remove(cookieName); +// }); +// window.location.href = "/"; +// } else { +// return { +// error: true, +// message: response?.data?.message || response?.data || null, +// data: null, +// }; +// } +// } export async function httpPostInterceptor( pathUrl: any, data?: any, @@ -67,19 +107,27 @@ export async function httpPostInterceptor( const resCsrf = await getCsrfToken(); const csrfToken = resCsrf?.data?.token; + const token = Cookies.get("token"); // JWT / session token + const clientKey = process.env.NEXT_PUBLIC_CLIENT_KEY; // dari .env.local + const defaultHeaders = { "Content-Type": "application/json", + ...(csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {}), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + ...(clientKey ? { "X-Client-Key": clientKey } : {}), }; + const mergedHeaders = { ...defaultHeaders, - ...(csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {}), ...headers, }; const response = await axiosInterceptorInstance .post(pathUrl, data, { headers: mergedHeaders }) .catch((error) => error.response); + console.log("Response interceptor : ", response); + if (response?.status == 200 || response?.status == 201) { return { error: false,