fixing sabda

This commit is contained in:
Sabda Yagra 2025-09-23 11:47:39 +07:00
parent 9fa3148d48
commit 78a4f6d1f8
16 changed files with 544 additions and 221 deletions

View File

@ -42,7 +42,7 @@ const AudioVisualTabs = () => {
<TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable />
<PendingApprovalTable typeId={2} />
</div>
</TabsContent>
</Tabs>

View File

@ -42,7 +42,11 @@ import TablePagination from "@/components/table/table-pagination";
import { listPendingApproval, PendingApprovalData } from "@/service/content/content";
import usePendingApprovalColumns from "./pending-approval-columns";
const PendingApprovalTable = () => {
type PendingApprovalTableProps = {
typeId?: number;
};
const PendingApprovalTable = ({ typeId }: PendingApprovalTableProps) => {
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<PendingApprovalData[]>([]);
const [totalData, setTotalData] = React.useState<number>(0);
@ -111,7 +115,7 @@ const PendingApprovalTable = () => {
async function fetchPendingData() {
try {
console.log("fetchPendingData: page =", page, "showData =", showData, "Number(showData) =", Number(showData));
const res = await listPendingApproval(page, Number(showData));
const res = await listPendingApproval(page, Number(showData), typeId);
if (res && !res.error && res.data) {
// Filter data based on search if provided

View File

@ -39,12 +39,12 @@ import columns from "./columns";
import { Label } from "@/components/ui/label";
import { format } from "date-fns";
import useTableColumns from "./columns";
import {
listEnableCategory,
import {
listEnableCategory,
listDataVideo,
listArticles,
listArticlesWithFilters,
ArticleFilters
ArticleFilters,
} from "@/service/content";
import {
SortingState,
@ -174,6 +174,7 @@ const TableVideo = () => {
const formattedEndDate = endDate
? format(new Date(endDate), "yyyy-MM-dd")
: "";
try {
// Using the new interface-based approach for video content
const filters: ArticleFilters = {
@ -182,15 +183,15 @@ const TableVideo = () => {
title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 2, // video 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;
@ -198,21 +199,80 @@ const TableVideo = () => {
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;
} else if (Array.isArray(data?.content)) {
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);
setTotalData(data?.totalElements ?? contentData.length);
setTotalPage(
data?.totalPages ?? Math.ceil(contentData.length / Number(showData))
);
} else {
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
} catch (error) {
console.error("Error fetching tasks:", error);
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
}
// 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 video content
// const filters: ArticleFilters = {
// page: page,
// totalPage: Number(showData),
// title: search || undefined,
// categoryId: categoryFilter ? Number(categoryFilter) : undefined,
// typeId: 2, // video 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 ?? contentData.length);
// setTotalPage(
// data?.totalPages ?? Math.ceil(contentData.length / Number(showData))
// );
// // setTotalData(data?.totalElements);
// // setTotalPage(data?.totalPages);
// }
// } catch (error) {
// console.error("Error fetching tasks:", error);
// }
// }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("title")?.setFilterValue(e.target.value); // Set filter tabel

View File

@ -42,7 +42,7 @@ const AudioTabs = () => {
<TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable />
<PendingApprovalTable typeId={4} />
</div>
</TabsContent>
</Tabs>

View File

@ -42,7 +42,11 @@ import TablePagination from "@/components/table/table-pagination";
import { listPendingApproval, PendingApprovalData } from "@/service/content/content";
import usePendingApprovalColumns from "./pending-approval-columns";
const PendingApprovalTable = () => {
type PendingApprovalTableProps = {
typeId?: number;
};
const PendingApprovalTable = ({ typeId }: PendingApprovalTableProps) => {
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<PendingApprovalData[]>([]);
const [totalData, setTotalData] = React.useState<number>(0);
@ -111,7 +115,7 @@ const PendingApprovalTable = () => {
async function fetchPendingData() {
try {
console.log("fetchPendingData: page =", page, "showData =", showData, "Number(showData) =", Number(showData));
const res = await listPendingApproval(page, Number(showData));
const res = await listPendingApproval(page, Number(showData), typeId);
if (res && !res.error && res.data) {
// Filter data based on search if provided

View File

@ -171,51 +171,106 @@ const TableAudio = () => {
});
};
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 audio content
const filters: ArticleFilters = {
page: page,
totalPage: Number(showData),
title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 4, // audio 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);
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 video content
const filters: ArticleFilters = {
page: page,
totalPage: Number(showData),
title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 2, // video 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;
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 if (Array.isArray(data?.content)) {
const contentData = data.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
setTotalData(data?.totalElements ?? contentData.length);
setTotalPage(
data?.totalPages ?? Math.ceil(contentData.length / Number(showData))
);
} else {
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
} catch (error) {
console.error("Error fetching tasks:", error);
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
// 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 audio content
// const filters: ArticleFilters = {
// page: page,
// totalPage: Number(showData),
// title: search || undefined,
// categoryId: categoryFilter ? Number(categoryFilter) : undefined,
// typeId: 4, // audio 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);
// }
// }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search

View File

@ -42,7 +42,7 @@ const DocumentTabs = () => {
<TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable />
<PendingApprovalTable typeId={3} />
</div>
</TabsContent>
</Tabs>

View File

@ -42,7 +42,11 @@ import TablePagination from "@/components/table/table-pagination";
import { listPendingApproval, PendingApprovalData } from "@/service/content/content";
import usePendingApprovalColumns from "./pending-approval-columns";
const PendingApprovalTable = () => {
type PendingApprovalTableProps = {
typeId?: number;
};
const PendingApprovalTable = ({ typeId }: PendingApprovalTableProps) => {
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<PendingApprovalData[]>([]);
const [totalData, setTotalData] = React.useState<number>(0);
@ -95,7 +99,7 @@ const PendingApprovalTable = () => {
async function fetchPendingData() {
try {
const res = await listPendingApproval(page, Number(showData));
const res = await listPendingApproval(page, Number(showData), typeId);
if (res && !res.error && res.data) {
// Filter data based on search if provided

View File

@ -177,23 +177,24 @@ const TableTeks = () => {
const formattedEndDate = endDate
? format(new Date(endDate), "yyyy-MM-dd")
: "";
try {
// Using the new interface-based approach for cleaner code
// Using the new interface-based approach for video content
const filters: ArticleFilters = {
page: page,
totalPage: Number(showData),
title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 3, // text content type
statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
typeId: 2, // video 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;
@ -201,21 +202,75 @@ const TableTeks = () => {
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;
} else if (Array.isArray(data?.content)) {
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);
setTotalData(data?.totalElements ?? contentData.length);
setTotalPage(
data?.totalPages ?? Math.ceil(contentData.length / Number(showData))
);
} else {
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
} catch (error) {
console.error("Error fetching tasks:", error);
setDataTable([]);
setTotalData(0);
setTotalPage(1);
}
}
// 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 cleaner code
// const filters: ArticleFilters = {
// page: page,
// totalPage: Number(showData),
// title: search || undefined,
// categoryId: categoryFilter ? Number(categoryFilter) : undefined,
// typeId: 3, // text 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);
// }
// }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel

View File

@ -42,7 +42,7 @@ const ImageTabs = () => {
<TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable />
<PendingApprovalTable typeId={1} />
</div>
</TabsContent>
</Tabs>

View File

@ -43,7 +43,11 @@ import { listPendingApproval, PendingApprovalData } from "@/service/content/cont
import usePendingApprovalColumns from "./pending-approval-columns";
import { fi } from "date-fns/locale";
const PendingApprovalTable = () => {
type PendingApprovalTableProps = {
typeId?: number;
};
const PendingApprovalTable = ({ typeId }: PendingApprovalTableProps) => {
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<PendingApprovalData[]>([]);
const [totalData, setTotalData] = React.useState<number>(0);
@ -112,7 +116,7 @@ const PendingApprovalTable = () => {
async function fetchPendingData() {
try {
console.log("fetchPendingData: page =", page, "showData =", showData, "Number(showData) =", Number(showData));
const res = await listPendingApproval(page, Number(showData));
const res = await listPendingApproval(page, Number(showData), typeId);
if (res && !res.error && res.data.data) {
// Filter data based on search if provided

View File

@ -123,6 +123,7 @@ export default function FormVideo() {
const [publishedForError, setPublishedForError] = useState<string | null>(
null
);
const userId = Cookies.get("userId");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
@ -478,24 +479,26 @@ export default function FormVideo() {
// 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);
@ -516,7 +519,8 @@ export default function FormVideo() {
// 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);
@ -577,6 +581,23 @@ export default function FormVideo() {
return;
}
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())
);
}
let requestData: {
title: string;
description: string;
@ -618,16 +639,22 @@ export default function FormVideo() {
if (id == undefined) {
// New Articles API request data structure
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: 4, // Video content type
tags: finalTags,
isDraft: true,
isPublish: false,
oldId: 0,
slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
slug: finalTitle
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, ""),
tags: finalTags,
title: finalTitle,
typeId: 1, // Image content type
};
// Use new Articles API
@ -636,7 +663,11 @@ export default function FormVideo() {
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;
}
@ -647,53 +678,69 @@ export default function FormVideo() {
// Upload files using new article-files API
const formData = new FormData();
// Add all files to FormData
files.forEach((file, index) => {
formData.append('files', 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
if (files.length > 0) {
const thumbnailFormData = new FormData();
thumbnailFormData.append('files', files[0]); // Use first file as thumbnail
thumbnailFormData.append("files", files[0]); // Use first file as thumbnail
console.log("Uploading thumbnail for article:", articleId);
try {
const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData);
const thumbnailResponse = await uploadArticleThumbnail(
articleId,
thumbnailFormData
);
if (thumbnailResponse?.error) {
console.warn("Thumbnail upload failed:", thumbnailResponse.message);
console.warn(
"Thumbnail upload failed:",
thumbnailResponse.message
);
// Don't fail the whole process if thumbnail upload fails
} else {
console.log("Thumbnail uploaded successfully:", thumbnailResponse);
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) {
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",
@ -702,13 +749,13 @@ export default function FormVideo() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/admin/content/video");
router.push("/admin/content/audio-visual");
});
Cookies.remove("idCreate");
return;
}
Cookies.remove("idCreate");
};
@ -985,7 +1032,6 @@ export default function FormVideo() {
name="categoryId"
render={({ field }) => (
<div className="w-full">
<Label>Category</Label>
<Select
value={field.value}
onValueChange={(value) => {

View File

@ -147,6 +147,7 @@ export default function FormAudio() {
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [fileError, setFileError] = useState<string | null>(null);
type FileWithPreview = File & { preview: string };
const userId = Cookies.get("userId");
const options: Option[] = [
{ id: "all", label: "SEMUA" },
@ -220,8 +221,7 @@ export default function FormAudio() {
file.size <= 100 * 1024 * 1024
),
{
message:
"Hanya file .mp3, .wav, maksimal 100MB yang diperbolehkan.",
message: "Hanya file .mp3, .wav, maksimal 100MB yang diperbolehkan.",
}
),
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
@ -480,24 +480,26 @@ export default function FormAudio() {
// 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);
@ -518,7 +520,8 @@ export default function FormAudio() {
// 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);
@ -575,6 +578,23 @@ export default function FormAudio() {
return;
}
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())
);
}
let requestData: {
title: string;
description: string;
@ -616,16 +636,22 @@ export default function FormAudio() {
if (id == undefined) {
// New Articles API request data structure
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: 3, // Audio content type
tags: finalTags,
isDraft: true,
isPublish: false,
oldId: 0,
slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
slug: finalTitle
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, ""),
tags: finalTags,
title: finalTitle,
typeId: 1, // Image content type
};
// Use new Articles API
@ -634,7 +660,11 @@ export default function FormAudio() {
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;
}
@ -645,53 +675,69 @@ export default function FormAudio() {
// Upload files using new article-files API
const formData = new FormData();
// Add all files to FormData
files.forEach((file, index) => {
formData.append('files', 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
if (files.length > 0) {
const thumbnailFormData = new FormData();
thumbnailFormData.append('files', files[0]); // Use first file as thumbnail
thumbnailFormData.append("files", files[0]); // Use first file as thumbnail
console.log("Uploading thumbnail for article:", articleId);
try {
const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData);
const thumbnailResponse = await uploadArticleThumbnail(
articleId,
thumbnailFormData
);
if (thumbnailResponse?.error) {
console.warn("Thumbnail upload failed:", thumbnailResponse.message);
console.warn(
"Thumbnail upload failed:",
thumbnailResponse.message
);
// Don't fail the whole process if thumbnail upload fails
} else {
console.log("Thumbnail uploaded successfully:", thumbnailResponse);
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) {
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",
@ -702,11 +748,11 @@ export default function FormAudio() {
}).then(() => {
router.push("/admin/content/audio");
});
Cookies.remove("idCreate");
return;
}
Cookies.remove("idCreate");
};
@ -975,7 +1021,6 @@ export default function FormAudio() {
name="categoryId"
render={({ field }) => (
<div className="w-full">
<Label>Category</Label>
<Select
value={field.value}
onValueChange={(value) => {

View File

@ -131,7 +131,7 @@ export default function FormTeks() {
polda: false,
polres: false,
});
const userId = Cookies.get("userId");
let fileTypeId = "2";
let progressInfo: any = [];
let counterUpdateProgress = 0;
@ -476,24 +476,26 @@ export default function FormTeks() {
// 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);
@ -514,7 +516,8 @@ export default function FormTeks() {
// 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);
@ -579,6 +582,23 @@ export default function FormTeks() {
return;
}
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())
);
}
let requestData: {
title: string;
description: string;
@ -620,25 +640,34 @@ export default function FormTeks() {
if (id == undefined) {
// New Articles API request data structure
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: 2, // Document content type
tags: finalTags,
isDraft: true,
isPublish: false,
oldId: 0,
slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
slug: finalTitle
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, ""),
tags: finalTags,
title: finalTitle,
typeId: 1, // Image content type
};
// 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;
}
@ -649,53 +678,69 @@ export default function FormTeks() {
// Upload files using new article-files API
const formData = new FormData();
// Add all files to FormData
files.forEach((file, index) => {
formData.append('files', 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
if (files.length > 0) {
const thumbnailFormData = new FormData();
thumbnailFormData.append('files', files[0]); // Use first file as thumbnail
thumbnailFormData.append("files", files[0]); // Use first file as thumbnail
console.log("Uploading thumbnail for article:", articleId);
try {
const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData);
const thumbnailResponse = await uploadArticleThumbnail(
articleId,
thumbnailFormData
);
if (thumbnailResponse?.error) {
console.warn("Thumbnail upload failed:", thumbnailResponse.message);
console.warn(
"Thumbnail upload failed:",
thumbnailResponse.message
);
// Don't fail the whole process if thumbnail upload fails
} else {
console.log("Thumbnail uploaded successfully:", thumbnailResponse);
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) {
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",
@ -706,11 +751,11 @@ export default function FormTeks() {
}).then(() => {
router.push("/admin/content/document");
});
Cookies.remove("idCreate");
return;
}
Cookies.remove("idCreate");
};
@ -963,42 +1008,41 @@ export default function FormTeks() {
<div className="flex items-center">
<div className="py-3 w-full space-y-2">
<Label>Category</Label>
<Controller
control={control}
name="categoryId"
render={({ field }) => (
<div className="w-full">
<Label>Category</Label>
<Select
value={field.value}
onValueChange={(value) => {
field.onChange(value);
setSelectedCategory(value);
}}
>
<SelectTrigger>
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Controller
control={control}
name="categoryId"
render={({ field }) => (
<div className="w-full">
<Select
value={field.value}
onValueChange={(value) => {
field.onChange(value);
setSelectedCategory(value);
}}
>
<SelectTrigger>
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.categoryId && (
<p className="text-sm text-red-500 mt-1">
{errors.categoryId.message}
</p>
{errors.categoryId && (
<p className="text-sm text-red-500 mt-1">
{errors.categoryId.message}
</p>
)}
</div>
)}
</div>
)}
/>
/>
</div>
</div>
<div className="flex flex-row items-center gap-3 py-2">

1
package-lock.json generated
View File

@ -2338,6 +2338,7 @@
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
"integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-context": "1.1.2",

View File

@ -456,9 +456,10 @@ export interface PendingApprovalResponse {
// Function to fetch pending approval data
export async function listPendingApproval(
page: number = 1,
limit: number = 10
limit: number = 10,
typeId?: number
) {
const url = `articles/pending-approval?page=${page}&limit=${limit}`;
const url = `articles/pending-approval?page=${page}&limit=${limit}&typeId=${typeId}`;
return await httpGetInterceptor(url);
}