fixing sabda
This commit is contained in:
parent
5fdcfdfdb9
commit
4f02c4ae18
|
|
@ -15,7 +15,7 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { deleteMedia } from "@/service/content/content";
|
import { deleteMedia } from "@/service/content/content";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -41,7 +41,7 @@ const useTableColumns = () => {
|
||||||
{
|
{
|
||||||
accessorKey: "title",
|
accessorKey: "title",
|
||||||
header: "Title",
|
header: "Title",
|
||||||
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
|
cell: ({ row }) => {
|
||||||
const title: string = row.getValue("title");
|
const title: string = row.getValue("title");
|
||||||
return (
|
return (
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
|
|
@ -56,24 +56,17 @@ const useTableColumns = () => {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const categoryName = row.getValue("categoryName");
|
const categoryName = row.getValue("categoryName");
|
||||||
const categories = row.original.categories;
|
const categories = row.original.categories;
|
||||||
// Handle new API structure with categories array
|
const displayName =
|
||||||
const displayName = categoryName || (categories && categories.length > 0 ? categories[0].title : "-");
|
categoryName ||
|
||||||
return (
|
(categories && categories.length > 0 ? categories[0].title : "-");
|
||||||
<span className="whitespace-nowrap">
|
return <span className="whitespace-nowrap">{displayName}</span>;
|
||||||
{displayName}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "createdAt",
|
accessorKey: "createdAt",
|
||||||
header: "Upload Date",
|
header: "Upload Date",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const createdAt = row.getValue("createdAt") as
|
const createdAt = row.getValue("createdAt") as string | number | undefined;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const formattedDate =
|
const formattedDate =
|
||||||
createdAt && !isNaN(new Date(createdAt).getTime())
|
createdAt && !isNaN(new Date(createdAt).getTime())
|
||||||
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
|
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
|
||||||
|
|
@ -86,7 +79,7 @@ const useTableColumns = () => {
|
||||||
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>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -105,20 +98,19 @@ const useTableColumns = () => {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const isPublish = row.original.isPublish;
|
const isPublish = row.original.isPublish;
|
||||||
const isPublishOnPolda = row.original.isPublishOnPolda;
|
const isPublishOnPolda = row.original.isPublishOnPolda;
|
||||||
const creatorGroupParentLevelId =
|
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
|
||||||
row.original.creatorGroupParentLevelId;
|
|
||||||
|
|
||||||
let displayText = "-";
|
let displayText = "-";
|
||||||
if (isPublish && !isPublishOnPolda) {
|
if (isPublish && !isPublishOnPolda) {
|
||||||
displayText = "Mabes";
|
displayText = "Mabes";
|
||||||
} else if (isPublish && isPublishOnPolda) {
|
} else if (isPublish && isPublishOnPolda) {
|
||||||
if (Number(creatorGroupParentLevelId) == 761) {
|
if (Number(creatorGroupParentLevelId) === 761) {
|
||||||
displayText = "Mabes & Satker";
|
displayText = "Mabes & Satker";
|
||||||
} else {
|
} else {
|
||||||
displayText = "Mabes & Polda";
|
displayText = "Mabes & Polda";
|
||||||
}
|
}
|
||||||
} else if (!isPublish && isPublishOnPolda) {
|
} else if (!isPublish && isPublishOnPolda) {
|
||||||
if (Number(creatorGroupParentLevelId) == 761) {
|
if (Number(creatorGroupParentLevelId) === 761) {
|
||||||
displayText = "Satker";
|
displayText = "Satker";
|
||||||
} else {
|
} else {
|
||||||
displayText = "Polda";
|
displayText = "Polda";
|
||||||
|
|
@ -132,7 +124,6 @@ const useTableColumns = () => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//
|
|
||||||
{
|
{
|
||||||
accessorKey: "statusName",
|
accessorKey: "statusName",
|
||||||
header: "Status",
|
header: "Status",
|
||||||
|
|
@ -140,18 +131,13 @@ const useTableColumns = () => {
|
||||||
const statusId = Number(row.original?.statusId);
|
const statusId = Number(row.original?.statusId);
|
||||||
const reviewedAtLevel = row.original?.reviewedAtLevel || "";
|
const reviewedAtLevel = row.original?.reviewedAtLevel || "";
|
||||||
const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId);
|
const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId);
|
||||||
const needApprovalFromLevel = Number(
|
const needApprovalFromLevel = Number(row.original?.needApprovalFromLevel);
|
||||||
row.original?.needApprovalFromLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
||||||
const isCreator = creatorGroupLevelId === Number(userLevelId);
|
const isCreator = creatorGroupLevelId === Number(userLevelId);
|
||||||
|
|
||||||
const isWaitingForReview =
|
const isWaitingForReview = statusId === 2 && !userHasReviewed && !isCreator;
|
||||||
statusId === 2 && !userHasReviewed && !isCreator;
|
const isApprovalNeeded = statusId === 1 && needApprovalFromLevel === Number(userLevelId);
|
||||||
|
|
||||||
const isApprovalNeeded =
|
|
||||||
statusId === 1 && needApprovalFromLevel === Number(userLevelId);
|
|
||||||
|
|
||||||
const label =
|
const label =
|
||||||
isWaitingForReview || isApprovalNeeded
|
isWaitingForReview || isApprovalNeeded
|
||||||
|
|
@ -169,18 +155,12 @@ const useTableColumns = () => {
|
||||||
const statusStyles = colors[label] || colors.default;
|
const statusStyles = colors[label] || colors.default;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge className={cn("rounded-full px-5 w-full whitespace-nowrap", statusStyles)}>
|
||||||
className={cn(
|
|
||||||
"rounded-full px-5 w-full whitespace-nowrap",
|
|
||||||
statusStyles
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
|
|
@ -191,13 +171,8 @@ const useTableColumns = () => {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
async function doDelete(id: any) {
|
async function doDelete(id: any) {
|
||||||
// loading();
|
const data = { id };
|
||||||
const data = {
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await deleteMedia(data);
|
const response = await deleteMedia(data);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response.message);
|
error(response.message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -221,7 +196,6 @@ const useTableColumns = () => {
|
||||||
const handleDeleteMedia = (id: any) => {
|
const handleDeleteMedia = (id: any) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Hapus Data",
|
title: "Hapus Data",
|
||||||
text: "",
|
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
cancelButtonColor: "#3085d6",
|
cancelButtonColor: "#3085d6",
|
||||||
|
|
@ -241,9 +215,7 @@ const useTableColumns = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (userLevelId !== undefined && roleId !== undefined) {
|
if (userLevelId !== undefined && roleId !== undefined) {
|
||||||
setIsMabesApprover(
|
setIsMabesApprover(Number(userLevelId) === 216 && Number(roleId) === 3);
|
||||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [userLevelId, roleId]);
|
}, [userLevelId, roleId]);
|
||||||
|
|
||||||
|
|
@ -263,23 +235,14 @@ const useTableColumns = () => {
|
||||||
href={`/admin/content/image/detail/${row.original.id}`}
|
href={`/admin/content/image/detail/${row.original.id}`}
|
||||||
className="hover:text-black"
|
className="hover:text-black"
|
||||||
>
|
>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none">
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
View
|
View
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Link
|
{(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && (
|
||||||
href={`/contributor/content/image/update/${row.original.id}`}
|
|
||||||
>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
|
||||||
Edit
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link> */}
|
|
||||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
|
||||||
isMabesApprover) && (
|
|
||||||
<Link href={`/admin/content/image/update/${row.original.id}`}>
|
<Link href={`/admin/content/image/update/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none">
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
@ -287,20 +250,11 @@ const useTableColumns = () => {
|
||||||
)}
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
onClick={() => 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"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
|
||||||
Hapus
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)} */}
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
async function fetchData() {
|
||||||
const formattedStartDate = startDate
|
const formattedStartDate = startDate
|
||||||
? format(new Date(startDate), "yyyy-MM-dd")
|
? format(new Date(startDate), "yyyy-MM-dd")
|
||||||
|
|
@ -185,42 +230,47 @@ const TableImage = () => {
|
||||||
const formattedEndDate = endDate
|
const formattedEndDate = endDate
|
||||||
? format(new Date(endDate), "yyyy-MM-dd")
|
? format(new Date(endDate), "yyyy-MM-dd")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Using the new interface-based approach for image 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: 1, // image content type
|
typeId: 1, // image content type
|
||||||
statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
|
statusId:
|
||||||
|
statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
|
||||||
startDate: formattedStartDate || undefined,
|
startDate: formattedStartDate || undefined,
|
||||||
endDate: formattedEndDate || undefined,
|
endDate: formattedEndDate || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await listArticlesWithFilters(filters);
|
const res = await listArticlesWithFilters(filters);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
// Handle new articles API response structure
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
data.forEach((item: any, index: number) => {
|
// ✅ aman karena data array
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
const processed = data.map((item: any, index: number) => ({
|
||||||
});
|
...item,
|
||||||
setDataTable(data);
|
no: (page - 1) * Number(showData) + index + 1,
|
||||||
|
}));
|
||||||
|
setDataTable(processed);
|
||||||
setTotalData(data.length);
|
setTotalData(data.length);
|
||||||
setTotalPage(Math.ceil(data.length / Number(showData)));
|
setTotalPage(Math.ceil(data.length / Number(showData)));
|
||||||
} else {
|
} else {
|
||||||
// Fallback to old structure if API still returns old format
|
// ✅ fallback kalau masih pakai struktur lama
|
||||||
const contentData = data?.content;
|
const contentData = Array.isArray(data?.content) ? data.content : [];
|
||||||
contentData.forEach((item: any, index: number) => {
|
const processed = contentData.map((item: any, index: number) => ({
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
...item,
|
||||||
});
|
no: (page - 1) * Number(showData) + index + 1,
|
||||||
setDataTable(contentData);
|
}));
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
setDataTable(processed);
|
||||||
|
setTotalData(data?.totalElements ?? 0);
|
||||||
|
setTotalPage(data?.totalPages ?? 1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Error fetching tasks:", error);
|
console.error("Error fetching tasks:", err);
|
||||||
|
setDataTable([]); // fallback aman
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ export default function FormImageDetail() {
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
const category = await listEnableCategory(fileTypeId);
|
||||||
const resCategory: Category[] = category?.data?.data?.content;
|
const resCategory: Category[] = category?.data;
|
||||||
|
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
console.log("data category", resCategory);
|
console.log("data category", resCategory);
|
||||||
|
|
@ -211,7 +211,7 @@ export default function FormImageDetail() {
|
||||||
// setValue("categoryId", findCategory.id);
|
// setValue("categoryId", findCategory.id);
|
||||||
setSelectedCategory(findCategory.id); // Set the selected category
|
setSelectedCategory(findCategory.id); // Set the selected category
|
||||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
setTags(response?.data?.data);
|
setTags(response?.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -227,40 +227,95 @@ export default function FormImageDetail() {
|
||||||
setFilePlacements(temp);
|
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(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
const response = await detailMedia(id);
|
const response = await detailMedia(id);
|
||||||
const details = response?.data?.data;
|
const details = response?.data;
|
||||||
console.log("detail", details);
|
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(
|
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
|
// Set target category
|
||||||
setSelectedTarget(String(details.category.id));
|
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 || [];
|
// Thumbnails
|
||||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
const filesData = details?.files || [];
|
||||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
const fileUrls = filesData.map((file: any) =>
|
||||||
|
file.file_thumbnail ? file.file_thumbnail : file.file_url
|
||||||
);
|
);
|
||||||
setDetailThumb(fileUrls);
|
setDetailThumb(fileUrls);
|
||||||
|
|
||||||
|
// Ambil approval
|
||||||
const approvals = await getDataApprovalByMediaUpload(details?.id);
|
const approvals = await getDataApprovalByMediaUpload(details?.id);
|
||||||
setApproval(approvals?.data?.data);
|
setApproval(approvals?.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -490,6 +545,7 @@ export default function FormImageDetail() {
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{/* Show the category from details if it doesn't exist in categories list */}
|
{/* Show the category from details if it doesn't exist in categories list */}
|
||||||
{detail &&
|
{detail &&
|
||||||
|
Array.isArray(categories) &&
|
||||||
!categories.find(
|
!categories.find(
|
||||||
(cat) =>
|
(cat) =>
|
||||||
String(cat.id) === String(detail.category.id)
|
String(cat.id) === String(detail.category.id)
|
||||||
|
|
@ -501,12 +557,13 @@ export default function FormImageDetail() {
|
||||||
{detail.category.name}
|
{detail.category.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
{categories.map((category) => (
|
|
||||||
|
{categories?.map((cat) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={String(category.id)}
|
key={String(cat.id)}
|
||||||
value={String(category.id)}
|
value={String(cat.id)}
|
||||||
>
|
>
|
||||||
{category.name}
|
{cat.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -479,19 +479,21 @@ export default function FormImage() {
|
||||||
console.error("Failed to fetch article categories:", category.message);
|
console.error("Failed to fetch article categories:", category.message);
|
||||||
// Fallback to old API if new one fails
|
// Fallback to old API if new one fails
|
||||||
const fallbackCategory = await listEnableCategory(fileTypeId);
|
const fallbackCategory = await listEnableCategory(fileTypeId);
|
||||||
const resCategory: Category[] = fallbackCategory?.data.data.content || [];
|
const resCategory: Category[] =
|
||||||
|
fallbackCategory?.data.data.content || [];
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new API response structure
|
// Handle new API response structure
|
||||||
const resCategory: Category[] = category?.data?.data?.map((item: any) => ({
|
const resCategory: Category[] =
|
||||||
id: item.id,
|
category?.data?.data?.map((item: any) => ({
|
||||||
name: item.title, // map title to name for backward compatibility
|
id: item.id,
|
||||||
title: item.title,
|
name: item.title, // map title to name for backward compatibility
|
||||||
description: item.description,
|
title: item.title,
|
||||||
...item
|
description: item.description,
|
||||||
})) || [];
|
...item,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
console.log("Article categories loaded:", resCategory);
|
console.log("Article categories loaded:", resCategory);
|
||||||
|
|
@ -512,7 +514,8 @@ export default function FormImage() {
|
||||||
// Fallback to old API if error occurs
|
// Fallback to old API if error occurs
|
||||||
try {
|
try {
|
||||||
const fallbackCategory = await listEnableCategory(fileTypeId);
|
const fallbackCategory = await listEnableCategory(fileTypeId);
|
||||||
const resCategory: Category[] = fallbackCategory?.data.data.content || [];
|
const resCategory: Category[] =
|
||||||
|
fallbackCategory?.data.data.content || [];
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
} catch (fallbackError) {
|
} catch (fallbackError) {
|
||||||
console.error("Fallback category fetch also failed:", fallbackError);
|
console.error("Fallback category fetch also failed:", fallbackError);
|
||||||
|
|
@ -550,6 +553,8 @@ export default function FormImage() {
|
||||||
}
|
}
|
||||||
}, [articleBody, setValue]);
|
}, [articleBody, setValue]);
|
||||||
|
|
||||||
|
const userId = Cookies.get("userId"); // atau dari auth context / localStorage
|
||||||
|
|
||||||
const save = async (data: ImageSchema) => {
|
const save = async (data: ImageSchema) => {
|
||||||
loading();
|
loading();
|
||||||
|
|
||||||
|
|
@ -560,7 +565,6 @@ export default function FormImage() {
|
||||||
|
|
||||||
const finalTags = tags.join(", ");
|
const finalTags = tags.join(", ");
|
||||||
const finalTitle = isSwitchOn ? title : data.title;
|
const finalTitle = isSwitchOn ? title : data.title;
|
||||||
// const finalDescription = articleBody || data.description;
|
|
||||||
const finalDescription = isSwitchOn
|
const finalDescription = isSwitchOn
|
||||||
? data.description
|
? data.description
|
||||||
: selectedFileType === "rewrite"
|
: selectedFileType === "rewrite"
|
||||||
|
|
@ -572,125 +576,94 @@ export default function FormImage() {
|
||||||
return;
|
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 = {
|
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),
|
description: htmlToString(finalDescription),
|
||||||
htmlDescription: finalDescription,
|
htmlDescription: finalDescription,
|
||||||
categoryIds: selectedCategory.toString(),
|
|
||||||
typeId: 1, // Image content type
|
|
||||||
tags: finalTags,
|
|
||||||
isDraft: true,
|
isDraft: true,
|
||||||
isPublish: false,
|
isPublish: false,
|
||||||
oldId: 0,
|
oldId: 0,
|
||||||
slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
|
slug: finalTitle
|
||||||
};
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
// Keep old structure for backward compatibility if needed
|
.replace(/[^a-z0-9-]/g, ""),
|
||||||
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,
|
|
||||||
tags: finalTags,
|
tags: finalTags,
|
||||||
isYoutube: false,
|
title: finalTitle,
|
||||||
isInternationalMedia: false,
|
typeId: 1, // Image content type
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = Cookies.get("idCreate");
|
let id = Cookies.get("idCreate");
|
||||||
|
|
||||||
if (scheduleId !== undefined) {
|
|
||||||
requestData.attachFromScheduleId = Number(scheduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == undefined) {
|
if (id == undefined) {
|
||||||
// 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);
|
||||||
|
|
||||||
if (response?.error) {
|
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;
|
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
|
// Upload files
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
files.forEach((file) => formData.append("files", file));
|
||||||
// Add all files to FormData
|
|
||||||
files.forEach((file, index) => {
|
|
||||||
formData.append('files', file);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Uploading files to article:", articleId);
|
|
||||||
console.log("Files to upload:", files.length);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uploadResponse = await uploadArticleFiles(articleId, formData);
|
const uploadResponse = await uploadArticleFiles(articleId, formData);
|
||||||
|
|
||||||
if (uploadResponse?.error) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Files uploaded successfully:", uploadResponse);
|
// Upload thumbnail pakai file pertama
|
||||||
|
|
||||||
// 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]);
|
||||||
|
await uploadArticleThumbnail(articleId, thumbnailFormData);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (uploadError) {
|
} catch (uploadError) {
|
||||||
console.error("Upload error:", 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;
|
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.",
|
||||||
|
|
@ -705,24 +678,6 @@ export default function FormImage() {
|
||||||
return;
|
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");
|
Cookies.remove("idCreate");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -755,13 +710,12 @@ export default function FormImage() {
|
||||||
|
|
||||||
const resCsrf = await getCsrfToken();
|
const resCsrf = await getCsrfToken();
|
||||||
const csrfToken = resCsrf?.data?.token;
|
const csrfToken = resCsrf?.data?.token;
|
||||||
console.log("CSRF TOKEN : ", csrfToken);
|
|
||||||
const headers = {
|
const headers = {
|
||||||
"X-XSRF-TOKEN": csrfToken,
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
const upload = new Upload(file, {
|
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,
|
headers: headers,
|
||||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
chunkSize: 20_000,
|
chunkSize: 20_000,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
"use client";
|
"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 Image from "next/image";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
|
|
@ -15,8 +21,27 @@ interface RetractingSidebarProps {
|
||||||
sidebarData: boolean;
|
sidebarData: boolean;
|
||||||
updateSidebarData: (newData: boolean) => void;
|
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",
|
title: "Dashboard",
|
||||||
items: [
|
items: [
|
||||||
|
|
@ -33,29 +58,37 @@ const sidebarSections = [
|
||||||
title: "Content",
|
title: "Content",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "Foto",
|
title: "Master Data",
|
||||||
icon: () => <Icon icon="ri:article-line" className="text-lg" />,
|
icon: () => <Icon icon="mdi:folder-outline" className="text-lg" />,
|
||||||
link: "/admin/content/image",
|
children: [
|
||||||
},
|
{
|
||||||
{
|
title: "Foto",
|
||||||
title: "Audio Visual",
|
icon: () => <Icon icon="ri:article-line" className="text-lg" />,
|
||||||
icon: () => <Icon icon="famicons:list-outline" className="text-lg" />,
|
link: "/admin/content/image",
|
||||||
link: "/admin/content/audio-visual",
|
},
|
||||||
},
|
{
|
||||||
{
|
title: "Audio Visual",
|
||||||
title: "Teks",
|
icon: () => (
|
||||||
icon: () => <Icon icon="ic:round-ads-click" className="text-lg" />,
|
<Icon icon="famicons:list-outline" className="text-lg" />
|
||||||
link: "/admin/content/document",
|
),
|
||||||
},
|
link: "/admin/content/audio-visual",
|
||||||
{
|
},
|
||||||
title: "Audio",
|
{
|
||||||
icon: () => (
|
title: "Teks",
|
||||||
<Icon
|
icon: () => <Icon icon="ic:round-ads-click" className="text-lg" />,
|
||||||
icon="material-symbols:comment-outline-rounded"
|
link: "/admin/content/document",
|
||||||
className="text-lg"
|
},
|
||||||
/>
|
{
|
||||||
),
|
title: "Audio",
|
||||||
link: "/admin/content/audio",
|
icon: () => (
|
||||||
|
<Icon
|
||||||
|
icon="material-symbols:comment-outline-rounded"
|
||||||
|
className="text-lg"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
link: "/admin/content/audio",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -174,6 +207,12 @@ const SidebarContent = ({
|
||||||
updateSidebarData: (newData: boolean) => void;
|
updateSidebarData: (newData: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
const [expanded, setExpanded] = useState<string | null>(null); // track parent yang dibuka
|
||||||
|
|
||||||
|
const toggleExpand = (title: string) => {
|
||||||
|
setExpanded((prev) => (prev === title ? null : title));
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||||
Cookies.remove(cookieName);
|
Cookies.remove(cookieName);
|
||||||
|
|
@ -181,13 +220,13 @@ const SidebarContent = ({
|
||||||
|
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* SCROLLABLE TOP SECTION */}
|
{/* SCROLLABLE TOP SECTION */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{/* HEADER SECTION */}
|
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex flex-col space-y-6">
|
||||||
{/* Logo and Toggle */}
|
{/* Logo */}
|
||||||
<div className="flex items-center justify-between px-4 py-6">
|
<div className="flex items-center justify-between px-4 py-6">
|
||||||
<Link href="/" className="flex items-center space-x-3">
|
<Link href="/" className="flex items-center space-x-3">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -230,35 +269,85 @@ const SidebarContent = ({
|
||||||
|
|
||||||
{/* Navigation Sections */}
|
{/* Navigation Sections */}
|
||||||
<div className="space-y-3 px-3 pb-6">
|
<div className="space-y-3 px-3 pb-6">
|
||||||
{sidebarSections.map((section, sectionIndex) => (
|
{sidebarSections.map((section) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={section.title}
|
key={section.title}
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.1 + sectionIndex * 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
className="space-y-3"
|
className="space-y-3"
|
||||||
>
|
>
|
||||||
{open && (
|
{open && (
|
||||||
<motion.h3
|
<motion.h3
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ delay: 0.2 + sectionIndex * 0.1 }}
|
transition={{ delay: 0.2 }}
|
||||||
className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3"
|
className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3"
|
||||||
>
|
>
|
||||||
{section.title}
|
{section.title}
|
||||||
</motion.h3>
|
</motion.h3>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{section.items.map((item, itemIndex) => (
|
{section.items.map((item) =>
|
||||||
<Link href={item.link} key={item.title}>
|
"children" in item ? (
|
||||||
<Option
|
<div key={item.title}>
|
||||||
Icon={item.icon}
|
{/* Parent menu dengan toggle */}
|
||||||
title={item.title}
|
<div
|
||||||
active={pathname === item.link}
|
onClick={() => toggleExpand(item.title)}
|
||||||
open={open}
|
className="w-full flex items-center justify-between pr-2"
|
||||||
/>
|
>
|
||||||
</Link>
|
<Option
|
||||||
))}
|
Icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
active={false}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
{open && (
|
||||||
|
<motion.span
|
||||||
|
animate={{
|
||||||
|
rotate: expanded === item.title ? 90 : 0,
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="text-slate-500"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:chevron-right" />
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Children expand/collapse */}
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
height: expanded === item.title ? "auto" : 0,
|
||||||
|
opacity: expanded === item.title ? 1 : 0,
|
||||||
|
}}
|
||||||
|
className="overflow-hidden ml-6 space-y-1"
|
||||||
|
>
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<Link href={child.link} key={child.title}>
|
||||||
|
<Option
|
||||||
|
Icon={child.icon}
|
||||||
|
title={child.title}
|
||||||
|
active={pathname === child.link}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link href={item.link} key={item.title}>
|
||||||
|
<Option
|
||||||
|
Icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
active={pathname === item.link}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -266,148 +355,20 @@ const SidebarContent = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* FIXED BOTTOM SECTION */}
|
{/* ... (BOTTOM SECTION tetap sama) */}
|
||||||
<div className="flex-shrink-0 space-y-1 border-t border-slate-200/60 dark:border-slate-700/60 bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm">
|
|
||||||
{/* Divider */}
|
|
||||||
{/* <div className="px-3 pb-2">
|
|
||||||
<div className="h-px bg-gradient-to-r from-transparent via-slate-300 to-transparent"></div>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Theme Toggle */}
|
|
||||||
<div className="px-3 pt-1">
|
|
||||||
<motion.button
|
|
||||||
onClick={toggleTheme}
|
|
||||||
className={`relative flex h-12 w-full items-center rounded-xl transition-all duration-200 cursor-pointer group ${
|
|
||||||
open ? "px-3" : "justify-center"
|
|
||||||
} ${
|
|
||||||
theme === "dark"
|
|
||||||
? "bg-gradient-to-r from-emerald-500 to-green-500 text-white shadow-lg shadow-emerald-500/25"
|
|
||||||
: "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800 dark:text-slate-300 dark:hover:bg-slate-700/50"
|
|
||||||
}`}
|
|
||||||
whileHover={{ scale: 1.02 }}
|
|
||||||
whileTap={{ scale: 0.98 }}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
className={`h-full flex items-center justify-center ${
|
|
||||||
open ? "w-12" : "w-full"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`text-lg transition-all duration-200 ${
|
|
||||||
theme === "dark"
|
|
||||||
? "text-white"
|
|
||||||
: "text-slate-500 group-hover:text-slate-700 dark:text-slate-400 dark:group-hover:text-slate-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{theme === "dark" ? (
|
|
||||||
<Icon icon="solar:sun-bold" className="text-lg" />
|
|
||||||
) : (
|
|
||||||
<Icon icon="solar:moon-bold" className="text-lg" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{open && (
|
|
||||||
<motion.span
|
|
||||||
initial={{ opacity: 0, x: -10 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 0.1, duration: 0.2 }}
|
|
||||||
className={`text-sm font-medium transition-colors duration-200 ${
|
|
||||||
theme === "dark"
|
|
||||||
? "text-white"
|
|
||||||
: "text-slate-700 dark:text-slate-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{theme === "dark" ? "Light Mode" : "Dark Mode"}
|
|
||||||
</motion.span>
|
|
||||||
)}
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Settings */}
|
|
||||||
<div className="px-3">
|
|
||||||
<Link href="/settings">
|
|
||||||
<Option
|
|
||||||
Icon={() => (
|
|
||||||
<Icon icon="lets-icons:setting-fill" className="text-lg" />
|
|
||||||
)}
|
|
||||||
title="Settings"
|
|
||||||
active={pathname === "/settings"}
|
|
||||||
open={open}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* User Profile */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.4 }}
|
|
||||||
className="px-3 py-3 border-t border-slate-200/60"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
open
|
|
||||||
? "flex items-center space-x-3"
|
|
||||||
: "flex items-center justify-center"
|
|
||||||
} p-3 rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 hover:from-slate-100 hover:to-slate-200/50 transition-all duration-200 cursor-pointer group`}
|
|
||||||
>
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white font-semibold text-sm shadow-lg">
|
|
||||||
A
|
|
||||||
</div>
|
|
||||||
<div className="absolute -bottom-1 -right-1 w-4 h-4 bg-green-500 rounded-full border-2 border-white"></div>
|
|
||||||
</div>
|
|
||||||
{open && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -10 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 0.5 }}
|
|
||||||
className="flex-1 min-w-0"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium text-slate-800 truncate">
|
|
||||||
Mabes Polri - Approver
|
|
||||||
</p>
|
|
||||||
<Link href="/auth" onClick={handleLogout}>
|
|
||||||
<p className="text-xs text-slate-500 hover:text-blue-600 transition-colors duration-200">
|
|
||||||
Sign out
|
|
||||||
</p>
|
|
||||||
</Link>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Expand Button for Collapsed State */}
|
|
||||||
{/* {!open && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
transition={{ delay: 0.6 }}
|
|
||||||
className="px-3 pt-2"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => updateSidebarData(true)}
|
|
||||||
className="w-full p-3 rounded-xl bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white shadow-lg transition-all duration-200 hover:shadow-xl group"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<Icon
|
|
||||||
icon="heroicons:chevron-right"
|
|
||||||
className="w-5 h-5 group-hover:scale-110 transition-transform duration-200"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
const [expanded, setExpanded] = useState<string | null>(null); // track submenu yg dibuka
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const toggleExpand = (title: string) => {
|
||||||
|
setExpanded((prev) => (prev === title ? null : title));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.nav
|
<motion.nav
|
||||||
layout
|
layout
|
||||||
|
|
@ -424,25 +385,12 @@ const Sidebar = () => {
|
||||||
className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center"
|
className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center"
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
>
|
>
|
||||||
<svg
|
▶
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2.5"
|
|
||||||
d="m10 17l5-5m0 0l-5-5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Logo + tombol collapse */}
|
||||||
<div
|
<div
|
||||||
className={`flex ${
|
className={`flex ${
|
||||||
open ? "justify-between" : "justify-center"
|
open ? "justify-between" : "justify-center"
|
||||||
|
|
@ -456,39 +404,81 @@ const Sidebar = () => {
|
||||||
className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center"
|
className="w-5 h-5 text-zinc-400 border border-zinc-400 rounded-full flex justify-center items-center"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<svg
|
◀
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2.5"
|
|
||||||
d="m14 7l-5 5m0 0l5 5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
{/* Menu utama */}
|
||||||
|
<div className="space-y-3 mt-3">
|
||||||
{sidebarSections.map((section) => (
|
{sidebarSections.map((section) => (
|
||||||
<div key={section.title}>
|
<div key={section.title}>
|
||||||
<p className="font-bold text-[14px] py-2">{section.title}</p>
|
{open && (
|
||||||
{section.items.map((item) => (
|
<h3 className="px-2 text-xs font-semibold text-slate-500 uppercase tracking-wider">
|
||||||
<Link href={item.link} key={item.title}>
|
{section.title}
|
||||||
<Option
|
</h3>
|
||||||
Icon={item.icon}
|
)}
|
||||||
title={item.title}
|
|
||||||
active={pathname === item.link}
|
{section.items.map((item) =>
|
||||||
open={open}
|
"children" in item ? (
|
||||||
/>
|
<div key={item.title}>
|
||||||
</Link>
|
{/* Parent menu + chevron */}
|
||||||
))}
|
<button
|
||||||
|
onClick={() => toggleExpand(item.title)}
|
||||||
|
className="w-full flex items-center justify-between pr-2"
|
||||||
|
>
|
||||||
|
<Option
|
||||||
|
Icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
active={false}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
{/* Chevron animasi */}
|
||||||
|
{open && (
|
||||||
|
<motion.span
|
||||||
|
animate={{
|
||||||
|
rotate: expanded === item.title ? 90 : 0,
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="text-slate-500"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:chevron-right" />
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Children expand/collapse */}
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
height: expanded === item.title ? "auto" : 0,
|
||||||
|
opacity: expanded === item.title ? 1 : 0,
|
||||||
|
}}
|
||||||
|
className="overflow-hidden ml-6 space-y-1"
|
||||||
|
>
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<Link href={child.link} key={child.title}>
|
||||||
|
<Option
|
||||||
|
Icon={child.icon}
|
||||||
|
title={child.title}
|
||||||
|
active={pathname === child.link}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link href={item.link} key={item.title}>
|
||||||
|
<Option
|
||||||
|
Icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
active={pathname === item.link}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -511,8 +501,8 @@ const Sidebar = () => {
|
||||||
active={pathname === "/settings"}
|
active={pathname === "/settings"}
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
</Link>{" "}
|
</Link>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2 px-2">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="34"
|
width="34"
|
||||||
|
|
@ -526,7 +516,7 @@ const Sidebar = () => {
|
||||||
</svg>
|
</svg>
|
||||||
<div className="flex flex-col gap-0.5 text-xs">
|
<div className="flex flex-col gap-0.5 text-xs">
|
||||||
<p>admin-mabes</p>
|
<p>admin-mabes</p>
|
||||||
<p className="underline">Logout</p>
|
<p className="underline cursor-pointer">Logout</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -535,7 +525,6 @@ const Sidebar = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|
||||||
const TitleSection = ({ open }: { open: boolean }) => {
|
const TitleSection = ({ open }: { open: boolean }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex cursor-pointer items-center justify-between rounded-md transition-colors hover:bg-slate-100">
|
<div className="flex cursor-pointer items-center justify-between rounded-md transition-colors hover:bg-slate-100">
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,19 @@ export interface ArticleFilters {
|
||||||
|
|
||||||
// Interface for creating new article
|
// Interface for creating new article
|
||||||
export interface CreateArticleData {
|
export interface CreateArticleData {
|
||||||
title: string;
|
aiArticleId: number;
|
||||||
|
categoryIds: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdById: number;
|
||||||
description: string;
|
description: string;
|
||||||
htmlDescription: string;
|
htmlDescription: string;
|
||||||
categoryIds: string;
|
|
||||||
typeId: number;
|
|
||||||
tags: string;
|
|
||||||
isDraft: boolean;
|
isDraft: boolean;
|
||||||
isPublish: boolean;
|
isPublish: boolean;
|
||||||
oldId: number;
|
oldId: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
tags: string;
|
||||||
|
title: string;
|
||||||
|
typeId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface for Article Category
|
// Interface for Article Category
|
||||||
|
|
@ -240,7 +243,10 @@ export async function uploadThumbnail(id: any, data: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Articles API - Upload Article Files
|
// 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 url = `article-files/${articleId}`;
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
|
|
@ -249,7 +255,10 @@ export async function uploadArticleFiles(articleId: string | number, files: Form
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Articles API - Upload Article Thumbnail
|
// 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 url = `articles/thumbnail/${articleId}`;
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
|
|
@ -258,7 +267,10 @@ export async function uploadArticleThumbnail(articleId: string | number, thumbna
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Articles API - Get Article Categories
|
// 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}`;
|
const url = `article-categories?page=${page}&limit=${limit}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +406,8 @@ export async function listDataTeksNew(
|
||||||
) {
|
) {
|
||||||
// Convert old parameters to new API format
|
// Convert old parameters to new API format
|
||||||
const categoryId = categoryFilter ? Number(categoryFilter) : undefined;
|
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(
|
return await listArticles(
|
||||||
page + 1, // API expects 1-based page
|
page + 1, // API expects 1-based page
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
||||||
export async function detailMedia(id: any) {
|
export async function detailMedia(id: any) {
|
||||||
const url = `media?id=${id}`;
|
const url = `articles?id=${id}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
export async function httpPostInterceptor(
|
||||||
pathUrl: any,
|
pathUrl: any,
|
||||||
data?: any,
|
data?: any,
|
||||||
|
|
@ -67,19 +107,27 @@ export async function httpPostInterceptor(
|
||||||
const resCsrf = await getCsrfToken();
|
const resCsrf = await getCsrfToken();
|
||||||
const csrfToken = resCsrf?.data?.token;
|
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 = {
|
const defaultHeaders = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
...(csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {}),
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
...(clientKey ? { "X-Client-Key": clientKey } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergedHeaders = {
|
const mergedHeaders = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
...(csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {}),
|
|
||||||
...headers,
|
...headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axiosInterceptorInstance
|
const response = await axiosInterceptorInstance
|
||||||
.post(pathUrl, data, { headers: mergedHeaders })
|
.post(pathUrl, data, { headers: mergedHeaders })
|
||||||
.catch((error) => error.response);
|
.catch((error) => error.response);
|
||||||
|
|
||||||
console.log("Response interceptor : ", response);
|
console.log("Response interceptor : ", response);
|
||||||
|
|
||||||
if (response?.status == 200 || response?.status == 201) {
|
if (response?.status == 200 || response?.status == 201) {
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue