feat: update create form content and public landing
This commit is contained in:
parent
d07e92aa2a
commit
5fdcfdfdb9
|
|
@ -41,11 +41,18 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
import {
|
import {
|
||||||
listEnableCategory,
|
|
||||||
getTagsBySubCategoryId,
|
|
||||||
createMedia,
|
createMedia,
|
||||||
|
createArticle,
|
||||||
|
getTagsBySubCategoryId,
|
||||||
|
listEnableCategory,
|
||||||
|
listArticleCategories,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
} from "@/service/content";
|
uploadArticleFiles,
|
||||||
|
uploadArticleThumbnail,
|
||||||
|
CreateArticleData,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { request } from "http";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { htmlToString } from "@/utils/globals";
|
import { htmlToString } from "@/utils/globals";
|
||||||
import {
|
import {
|
||||||
generateDataArticle,
|
generateDataArticle,
|
||||||
|
|
@ -54,6 +61,7 @@ import {
|
||||||
getGenerateKeywords,
|
getGenerateKeywords,
|
||||||
getGenerateTitle,
|
getGenerateTitle,
|
||||||
} from "@/service/content/ai";
|
} from "@/service/content/ai";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -188,27 +196,30 @@ export default function FormVideo() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoSchema = z.object({
|
const videoSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "titleRequired" }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(),
|
descriptionOri: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(),
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||||
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
|
||||||
tags: z
|
|
||||||
.array(z.string().min(1))
|
|
||||||
.min(1, { message: "Minimal 1 tag diperlukan" }),
|
|
||||||
files: z
|
files: z
|
||||||
.array(z.any())
|
.array(z.any())
|
||||||
.min(1, { message: "File video harus dipilih" })
|
.min(1, { message: "Minimal 1 file harus diunggah." })
|
||||||
.refine(
|
.refine(
|
||||||
(files) =>
|
(files) =>
|
||||||
files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)),
|
files.every(
|
||||||
{ message: "File harus berformat mp4 atau mov" }
|
(file: File) =>
|
||||||
)
|
["video/mp4", "video/mov", "video/avi"].includes(file.type) &&
|
||||||
.refine(
|
file.size <= 100 * 1024 * 1024
|
||||||
(files) => files.every((file: File) => file.size <= MAX_FILE_SIZE),
|
),
|
||||||
{ message: "Ukuran file maksimal 100 MB" }
|
{
|
||||||
|
message:
|
||||||
|
"Hanya file .mp4, .mov, .avi, maksimal 100MB yang diperbolehkan.",
|
||||||
|
}
|
||||||
),
|
),
|
||||||
|
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
|
||||||
|
tags: z
|
||||||
|
.array(z.string())
|
||||||
|
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
||||||
publishedFor: z
|
publishedFor: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||||
|
|
@ -227,8 +238,9 @@ export default function FormVideo() {
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
category: "",
|
creatorName: "",
|
||||||
files: [],
|
files: [],
|
||||||
|
categoryId: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
publishedFor: [],
|
publishedFor: [],
|
||||||
},
|
},
|
||||||
|
|
@ -463,11 +475,30 @@ export default function FormVideo() {
|
||||||
|
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
// Use new Article Categories API
|
||||||
const resCategory: Category[] = category?.data?.data?.content;
|
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 || [];
|
||||||
|
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
|
||||||
|
})) || [];
|
||||||
|
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
console.log("data category", resCategory);
|
console.log("Article categories loaded:", resCategory);
|
||||||
|
|
||||||
if (scheduleId && scheduleType === "3") {
|
if (scheduleId && scheduleType === "3") {
|
||||||
const findCategory = resCategory.find((o) =>
|
const findCategory = resCategory.find((o) =>
|
||||||
|
|
@ -475,7 +506,6 @@ export default function FormVideo() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (findCategory) {
|
if (findCategory) {
|
||||||
// setValue("categoryId", findCategory.id);
|
|
||||||
setSelectedCategory(findCategory.id);
|
setSelectedCategory(findCategory.id);
|
||||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
setTags(response?.data?.data);
|
setTags(response?.data?.data);
|
||||||
|
|
@ -483,6 +513,14 @@ export default function FormVideo() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch categories:", error);
|
console.error("Failed to fetch categories:", error);
|
||||||
|
// Fallback to old API if error occurs
|
||||||
|
try {
|
||||||
|
const fallbackCategory = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = fallbackCategory?.data.data.content || [];
|
||||||
|
setCategories(resCategory);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback category fetch also failed:", fallbackError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -578,39 +616,98 @@ export default function FormVideo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == undefined) {
|
if (id == undefined) {
|
||||||
const response = await createMedia(requestData);
|
// New Articles API request data structure
|
||||||
console.log("Form Data Submitted:", requestData);
|
const articleData: CreateArticleData = {
|
||||||
|
title: finalTitle,
|
||||||
|
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, ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use new Articles API
|
||||||
|
const response = await createArticle(articleData);
|
||||||
|
console.log("Article Data Submitted:", articleData);
|
||||||
|
console.log("Article API Response:", response);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
MySwal.fire("Error", response?.message, "error");
|
MySwal.fire("Error", response.message || "Failed to create article", "error");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
|
||||||
id = response?.data?.data;
|
|
||||||
|
|
||||||
if (thumbnail) {
|
// Get the article ID from the new API response
|
||||||
const formMedia = new FormData();
|
const articleId = response?.data?.data?.id;
|
||||||
formMedia.append("file", thumbnail);
|
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||||
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
id = articleId;
|
||||||
if (responseThumbnail?.error) {
|
|
||||||
error(responseThumbnail.message);
|
// Upload files using new article-files API
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
const uploadResponse = await uploadArticleFiles(articleId, formData);
|
||||||
|
|
||||||
|
if (uploadResponse?.error) {
|
||||||
|
MySwal.fire("Error", uploadResponse.message || "Failed to upload files", "error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
const progressInfoArr = [];
|
|
||||||
for (const item of files) {
|
|
||||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
|
||||||
}
|
|
||||||
progressInfo = progressInfoArr;
|
|
||||||
setIsStartUpload(true);
|
|
||||||
setProgressList(progressInfoArr);
|
|
||||||
|
|
||||||
close();
|
console.log("Files uploaded successfully:", uploadResponse);
|
||||||
// showProgress();
|
|
||||||
files.map(async (item: any, index: number) => {
|
// Upload thumbnail using first file as thumbnail
|
||||||
await uploadResumableFile(index, String(id), item, "0");
|
if (files.length > 0) {
|
||||||
});
|
const thumbnailFormData = new FormData();
|
||||||
|
thumbnailFormData.append('files', files[0]); // Use first file as thumbnail
|
||||||
|
|
||||||
|
console.log("Uploading thumbnail for article:", articleId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData);
|
||||||
|
|
||||||
|
if (thumbnailResponse?.error) {
|
||||||
|
console.warn("Thumbnail upload failed:", thumbnailResponse.message);
|
||||||
|
// Don't fail the whole process if thumbnail upload fails
|
||||||
|
} else {
|
||||||
|
console.log("Thumbnail uploaded successfully:", thumbnailResponse);
|
||||||
|
}
|
||||||
|
} catch (thumbnailError) {
|
||||||
|
console.warn("Thumbnail upload error:", thumbnailError);
|
||||||
|
// Don't fail the whole process if thumbnail upload fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (uploadError) {
|
||||||
|
console.error("Upload error:", uploadError);
|
||||||
|
MySwal.fire("Error", "Failed to upload files. Please try again.", "error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Article dan files berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/admin/content/video");
|
||||||
|
});
|
||||||
|
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cookies.remove("idCreate");
|
Cookies.remove("idCreate");
|
||||||
};
|
};
|
||||||
|
|
@ -885,37 +982,40 @@ export default function FormVideo() {
|
||||||
<Label>Category</Label>
|
<Label>Category</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="category"
|
name="categoryId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<div className="w-full">
|
||||||
value={field.value}
|
<Label>Category</Label>
|
||||||
onValueChange={(id) => {
|
<Select
|
||||||
field.onChange(id);
|
value={field.value}
|
||||||
console.log("Selected Category ID:", id);
|
onValueChange={(value) => {
|
||||||
setSelectedCategory(id); // tetap set ini kalau mau
|
field.onChange(value);
|
||||||
}}
|
setSelectedCategory(value);
|
||||||
>
|
}}
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Pilih" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
{categories.map((category) => (
|
<SelectContent>
|
||||||
<SelectItem
|
{categories.map((category) => (
|
||||||
key={category.id}
|
<SelectItem
|
||||||
value={category.id.toString()}
|
key={category.id}
|
||||||
>
|
value={category.id.toString()}
|
||||||
{category.name}
|
>
|
||||||
</SelectItem>
|
{category.name}
|
||||||
))}
|
</SelectItem>
|
||||||
</SelectContent>
|
))}
|
||||||
</Select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{errors.categoryId && (
|
||||||
|
<p className="text-sm text-red-500 mt-1">
|
||||||
|
{errors.categoryId.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.category && (
|
|
||||||
<p className="text-red-500 text-sm">
|
|
||||||
{errors.category.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-2">
|
<div className="flex flex-row items-center gap-3 py-2">
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,14 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
createMedia,
|
createMedia,
|
||||||
|
createArticle,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
|
listArticleCategories,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
|
uploadArticleFiles,
|
||||||
|
uploadArticleThumbnail,
|
||||||
|
CreateArticleData,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
|
|
@ -54,6 +59,8 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
import { request } from "http";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { htmlToString } from "@/utils/globals";
|
import { htmlToString } from "@/utils/globals";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
|
@ -127,7 +134,7 @@ export default function FormAudio() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let fileTypeId = "4";
|
let fileTypeId = "3";
|
||||||
let progressInfo: any = [];
|
let progressInfo: any = [];
|
||||||
let counterUpdateProgress = 0;
|
let counterUpdateProgress = 0;
|
||||||
const [progressList, setProgressList] = useState<any>([]);
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
|
@ -197,18 +204,30 @@ export default function FormAudio() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const audioSchema = z.object({
|
const audioSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "titleRequired" }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(),
|
descriptionOri: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(),
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||||
category: z.string().min(1, { message: "Category diperlukan" }),
|
|
||||||
files: z
|
files: z
|
||||||
.array(z.any())
|
.array(z.any())
|
||||||
.min(1, { message: "Wajib upload minimal 1 file mp3 atau wav" }),
|
.min(1, { message: "Minimal 1 file harus diunggah." })
|
||||||
|
.refine(
|
||||||
|
(files) =>
|
||||||
|
files.every(
|
||||||
|
(file: File) =>
|
||||||
|
["audio/mpeg", "audio/wav", "audio/mp3"].includes(file.type) &&
|
||||||
|
file.size <= 100 * 1024 * 1024
|
||||||
|
),
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Hanya file .mp3, .wav, maksimal 100MB yang diperbolehkan.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
|
||||||
tags: z
|
tags: z
|
||||||
.array(z.string().min(1))
|
.array(z.string())
|
||||||
.min(1, { message: "Wajib isi minimal 1 tag" }),
|
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
||||||
publishedFor: z
|
publishedFor: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||||
|
|
@ -227,7 +246,9 @@ export default function FormAudio() {
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
category: "",
|
creatorName: "",
|
||||||
|
files: [],
|
||||||
|
categoryId: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
publishedFor: [],
|
publishedFor: [],
|
||||||
},
|
},
|
||||||
|
|
@ -456,11 +477,30 @@ export default function FormAudio() {
|
||||||
|
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
// Use new Article Categories API
|
||||||
const resCategory: Category[] = category?.data?.data?.content;
|
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 || [];
|
||||||
|
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
|
||||||
|
})) || [];
|
||||||
|
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
console.log("data category", resCategory);
|
console.log("Article categories loaded:", resCategory);
|
||||||
|
|
||||||
if (scheduleId && scheduleType === "3") {
|
if (scheduleId && scheduleType === "3") {
|
||||||
const findCategory = resCategory.find((o) =>
|
const findCategory = resCategory.find((o) =>
|
||||||
|
|
@ -468,7 +508,6 @@ export default function FormAudio() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (findCategory) {
|
if (findCategory) {
|
||||||
// setValue("categoryId", findCategory.id);
|
|
||||||
setSelectedCategory(findCategory.id);
|
setSelectedCategory(findCategory.id);
|
||||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
setTags(response?.data?.data);
|
setTags(response?.data?.data);
|
||||||
|
|
@ -476,6 +515,14 @@ export default function FormAudio() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch categories:", error);
|
console.error("Failed to fetch categories:", error);
|
||||||
|
// Fallback to old API if error occurs
|
||||||
|
try {
|
||||||
|
const fallbackCategory = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = fallbackCategory?.data.data.content || [];
|
||||||
|
setCategories(resCategory);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback category fetch also failed:", fallbackError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -567,43 +614,100 @@ export default function FormAudio() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == undefined) {
|
if (id == undefined) {
|
||||||
const response = await createMedia(requestData);
|
// New Articles API request data structure
|
||||||
console.log("Form Data Submitted:", requestData);
|
const articleData: CreateArticleData = {
|
||||||
|
title: finalTitle,
|
||||||
|
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, ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use new Articles API
|
||||||
|
const response = await createArticle(articleData);
|
||||||
|
console.log("Article Data Submitted:", articleData);
|
||||||
|
console.log("Article API Response:", response);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
MySwal.fire("Error", response?.message, "error");
|
MySwal.fire("Error", response.message || "Failed to create article", "error");
|
||||||
return;
|
|
||||||
}
|
|
||||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
|
||||||
id = response?.data?.data;
|
|
||||||
|
|
||||||
const formMedia = new FormData();
|
|
||||||
console.log("Thumbnail : ", files[0]);
|
|
||||||
formMedia.append("file", files[0]);
|
|
||||||
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
|
||||||
if (responseThumbnail?.error == true) {
|
|
||||||
error(responseThumbnail?.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const progressInfoArr = [];
|
// Get the article ID from the new API response
|
||||||
for (const item of files) {
|
const articleId = response?.data?.data?.id;
|
||||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||||
}
|
id = articleId;
|
||||||
progressInfo = progressInfoArr;
|
|
||||||
setIsStartUpload(true);
|
|
||||||
setProgressList(progressInfoArr);
|
|
||||||
|
|
||||||
close();
|
// Upload files using new article-files API
|
||||||
// showProgress();
|
const formData = new FormData();
|
||||||
files.map(async (item: any, index: number) => {
|
|
||||||
await uploadResumableFile(index, String(id), item, "0");
|
// 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 {
|
||||||
|
const uploadResponse = await uploadArticleFiles(articleId, formData);
|
||||||
|
|
||||||
|
if (uploadResponse?.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
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.error("Upload error:", uploadError);
|
||||||
|
MySwal.fire("Error", "Failed to upload files. Please try again.", "error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Article dan files berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/admin/content/audio");
|
||||||
|
});
|
||||||
|
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cookies.remove("idCreate");
|
Cookies.remove("idCreate");
|
||||||
|
|
||||||
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: AudioSchema) => {
|
const onSubmit = (data: AudioSchema) => {
|
||||||
|
|
@ -867,17 +971,16 @@ export default function FormAudio() {
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>Category</Label>
|
<Label>Category</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="category"
|
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ required: "Category is required" }}
|
name="categoryId"
|
||||||
render={({ field, fieldState }) => (
|
render={({ field }) => (
|
||||||
<>
|
<div className="w-full">
|
||||||
|
<Label>Category</Label>
|
||||||
<Select
|
<Select
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onValueChange={(id) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(id);
|
field.onChange(value);
|
||||||
console.log("Selected Category ID:", id);
|
setSelectedCategory(value);
|
||||||
setSelectedCategory(id);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -894,12 +997,13 @@ export default function FormAudio() {
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{fieldState.error && (
|
|
||||||
<p className="text-sm text-red-500">
|
{errors.categoryId && (
|
||||||
{fieldState.error.message}
|
<p className="text-sm text-red-500 mt-1">
|
||||||
|
{errors.categoryId.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,14 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
createMedia,
|
createMedia,
|
||||||
|
createArticle,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
|
listArticleCategories,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
|
uploadArticleFiles,
|
||||||
|
uploadArticleThumbnail,
|
||||||
|
CreateArticleData,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
|
|
@ -50,6 +55,8 @@ import Image from "next/image";
|
||||||
import { error, loading } from "@/config/swal";
|
import { error, loading } from "@/config/swal";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { request } from "http";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { htmlToString } from "@/utils/globals";
|
import { htmlToString } from "@/utils/globals";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
|
@ -125,7 +132,7 @@ export default function FormTeks() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let fileTypeId = "3";
|
let fileTypeId = "2";
|
||||||
let progressInfo: any = [];
|
let progressInfo: any = [];
|
||||||
let counterUpdateProgress = 0;
|
let counterUpdateProgress = 0;
|
||||||
const [progressList, setProgressList] = useState<any>([]);
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
|
@ -178,38 +185,34 @@ export default function FormTeks() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const teksSchema = z.object({
|
const teksSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "titleRequired" }),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
description: z.string().optional(),
|
||||||
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
descriptionOri: z.string().optional(),
|
||||||
tags: z
|
rewriteDescription: z.string().optional(),
|
||||||
.array(z.string())
|
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||||
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
|
||||||
files: z
|
files: z
|
||||||
.array(
|
.array(z.any())
|
||||||
z
|
.min(1, { message: "Minimal 1 file harus diunggah." })
|
||||||
.object({
|
.refine(
|
||||||
name: z.string(),
|
(files) =>
|
||||||
type: z.string(),
|
files.every(
|
||||||
size: z
|
(file: File) =>
|
||||||
.number()
|
|
||||||
.max(20 * 1024 * 1024, { message: "Max file size 20 MB" }),
|
|
||||||
})
|
|
||||||
.refine(
|
|
||||||
(file) =>
|
|
||||||
[
|
[
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
"application/msword",
|
"application/msword",
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"application/vnd.ms-powerpoint",
|
"text/plain",
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
].includes(file.type) && file.size <= 100 * 1024 * 1024
|
||||||
].includes(file.type),
|
),
|
||||||
{ message: "Format file tidak didukung" }
|
{
|
||||||
)
|
message:
|
||||||
)
|
"Hanya file .pdf, .doc, .docx, .txt, maksimal 100MB yang diperbolehkan.",
|
||||||
.min(1, { message: "File wajib diunggah" }),
|
}
|
||||||
description: z.string().optional(),
|
),
|
||||||
descriptionOri: z.string().optional(),
|
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
|
||||||
rewriteDescription: z.string().optional(),
|
tags: z
|
||||||
|
.array(z.string())
|
||||||
|
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
||||||
publishedFor: z
|
publishedFor: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||||
|
|
@ -226,13 +229,13 @@ export default function FormTeks() {
|
||||||
resolver: zodResolver(teksSchema),
|
resolver: zodResolver(teksSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
title: "",
|
title: "",
|
||||||
creatorName: "",
|
|
||||||
category: "",
|
|
||||||
tags: [],
|
|
||||||
files: [],
|
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
|
creatorName: "",
|
||||||
|
files: [],
|
||||||
|
categoryId: "",
|
||||||
|
tags: [],
|
||||||
publishedFor: [],
|
publishedFor: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -470,11 +473,30 @@ export default function FormTeks() {
|
||||||
|
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
// Use new Article Categories API
|
||||||
const resCategory: Category[] = category?.data?.data?.content;
|
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 || [];
|
||||||
|
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
|
||||||
|
})) || [];
|
||||||
|
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
console.log("data category", resCategory);
|
console.log("Article categories loaded:", resCategory);
|
||||||
|
|
||||||
if (scheduleId && scheduleType === "3") {
|
if (scheduleId && scheduleType === "3") {
|
||||||
const findCategory = resCategory.find((o) =>
|
const findCategory = resCategory.find((o) =>
|
||||||
|
|
@ -482,14 +504,21 @@ export default function FormTeks() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (findCategory) {
|
if (findCategory) {
|
||||||
// setValue("categoryId", findCategory.id);
|
setSelectedCategory(findCategory.id);
|
||||||
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?.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch categories:", error);
|
console.error("Failed to fetch categories:", error);
|
||||||
|
// Fallback to old API if error occurs
|
||||||
|
try {
|
||||||
|
const fallbackCategory = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = fallbackCategory?.data.data.content || [];
|
||||||
|
setCategories(resCategory);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback category fetch also failed:", fallbackError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -589,37 +618,98 @@ export default function FormTeks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == undefined) {
|
if (id == undefined) {
|
||||||
const response = await createMedia(requestData);
|
// New Articles API request data structure
|
||||||
console.log("Form Data Submitted:", requestData);
|
const articleData: CreateArticleData = {
|
||||||
|
title: finalTitle,
|
||||||
|
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, ''),
|
||||||
|
};
|
||||||
|
|
||||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
// Use new Articles API
|
||||||
id = response?.data?.data;
|
const response = await createArticle(articleData);
|
||||||
const formMedia = new FormData();
|
console.log("Article Data Submitted:", articleData);
|
||||||
const thumbnail = files[0];
|
console.log("Article API Response:", response);
|
||||||
formMedia.append("file", thumbnail);
|
|
||||||
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
if (response?.error) {
|
||||||
if (responseThumbnail?.error == true) {
|
MySwal.fire("Error", response.message || "Failed to create article", "error");
|
||||||
error(responseThumbnail?.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const progressInfoArr = files.map((item) => ({
|
|
||||||
percentage: 0,
|
|
||||||
fileName: item.name,
|
|
||||||
}));
|
|
||||||
progressInfo = progressInfoArr;
|
|
||||||
setIsStartUpload(true);
|
|
||||||
setProgressList(progressInfoArr);
|
|
||||||
|
|
||||||
close();
|
// Get the article ID from the new API response
|
||||||
files.map(async (item: any, index: number) => {
|
const articleId = response?.data?.data?.id;
|
||||||
await uploadResumableFile(
|
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||||
index,
|
id = articleId;
|
||||||
String(id),
|
|
||||||
item,
|
// Upload files using new article-files API
|
||||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
const formData = new FormData();
|
||||||
);
|
|
||||||
});
|
// 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 {
|
||||||
|
const uploadResponse = await uploadArticleFiles(articleId, formData);
|
||||||
|
|
||||||
|
if (uploadResponse?.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
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.error("Upload error:", uploadError);
|
||||||
|
MySwal.fire("Error", "Failed to upload files. Please try again.", "error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Article dan files berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/admin/content/document");
|
||||||
|
});
|
||||||
|
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cookies.remove("idCreate");
|
Cookies.remove("idCreate");
|
||||||
};
|
};
|
||||||
|
|
@ -848,7 +938,7 @@ export default function FormTeks() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">Form Text</p>
|
<p className="text-lg font-semibold mb-3">Form Document</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -873,38 +963,42 @@ export default function FormTeks() {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>Category</Label>
|
<Label>Category</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="category"
|
name="categoryId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<div className="w-full">
|
||||||
value={field.value}
|
<Label>Category</Label>
|
||||||
onValueChange={(val) => {
|
<Select
|
||||||
field.onChange(val);
|
value={field.value}
|
||||||
setSelectedCategory(val);
|
onValueChange={(value) => {
|
||||||
}}
|
field.onChange(value);
|
||||||
>
|
setSelectedCategory(value);
|
||||||
<SelectTrigger>
|
}}
|
||||||
<SelectValue placeholder="Pilih" />
|
>
|
||||||
</SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectContent>
|
<SelectValue placeholder="Pilih" />
|
||||||
{categories.map((category) => (
|
</SelectTrigger>
|
||||||
<SelectItem
|
<SelectContent>
|
||||||
key={category.id}
|
{categories.map((category) => (
|
||||||
value={category.id.toString()}
|
<SelectItem
|
||||||
>
|
key={category.id}
|
||||||
{category.name}
|
value={category.id.toString()}
|
||||||
</SelectItem>
|
>
|
||||||
))}
|
{category.name}
|
||||||
</SelectContent>
|
</SelectItem>
|
||||||
</Select>
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{errors.categoryId && (
|
||||||
|
<p className="text-sm text-red-500 mt-1">
|
||||||
|
{errors.categoryId.message}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
{errors.category?.message && (
|
)}
|
||||||
<p className="text-red-400 text-sm">
|
/>
|
||||||
{errors.category.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-2">
|
<div className="flex flex-row items-center gap-3 py-2">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
getListContent,
|
getListContent,
|
||||||
listData,
|
listData,
|
||||||
|
listArticles,
|
||||||
listStaticBanner,
|
listStaticBanner,
|
||||||
} from "@/service/landing/landing";
|
} from "@/service/landing/landing";
|
||||||
import { data } from "framer-motion/client";
|
import { data } from "framer-motion/client";
|
||||||
|
|
@ -17,25 +18,66 @@ export default function Header() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
// const request = {
|
// Use new Articles API
|
||||||
// group: "mabes",
|
const response = await listArticles(1, 5, undefined, undefined, undefined, "createdAt");
|
||||||
// };
|
console.log("Articles API response:", response);
|
||||||
const response = await listData(
|
|
||||||
"",
|
if (response?.error) {
|
||||||
"",
|
console.error("Articles API failed, falling back to old API");
|
||||||
"",
|
// Fallback to old API
|
||||||
5,
|
const fallbackResponse = await listData(
|
||||||
0,
|
"",
|
||||||
"createdAt",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
5,
|
||||||
""
|
0,
|
||||||
);
|
"createdAt",
|
||||||
const content = response?.data?.data?.content || [];
|
"",
|
||||||
console.log("data", content);
|
"",
|
||||||
setData(content);
|
""
|
||||||
|
);
|
||||||
|
const content = fallbackResponse?.data?.data?.content || [];
|
||||||
|
setData(content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articlesData = response?.data?.data || [];
|
||||||
|
console.log("Articles data:", articlesData);
|
||||||
|
|
||||||
|
// Transform articles data to match old structure for backward compatibility
|
||||||
|
const transformedData = articlesData.map((article: any) => ({
|
||||||
|
id: article.id,
|
||||||
|
title: article.title,
|
||||||
|
categoryName: article.categoryName || (article.categories && article.categories[0]?.title) || "",
|
||||||
|
createdAt: article.createdAt,
|
||||||
|
smallThumbnailLink: article.thumbnailUrl,
|
||||||
|
fileTypeId: article.typeId,
|
||||||
|
label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "",
|
||||||
|
...article
|
||||||
|
}));
|
||||||
|
|
||||||
|
setData(transformedData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal memuat data:", error);
|
console.error("Gagal memuat data:", error);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackResponse = await listData(
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
"createdAt",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
const content = fallbackResponse?.data?.data?.content || [];
|
||||||
|
setData(content);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -70,13 +112,13 @@ function Card({ item, isBig = false }: { item: any; isBig?: boolean }) {
|
||||||
const getLink = () => {
|
const getLink = () => {
|
||||||
switch (item?.fileTypeId) {
|
switch (item?.fileTypeId) {
|
||||||
case 1:
|
case 1:
|
||||||
return `/public/content/image/detail/${item?.id}`;
|
return `/content/image/detail/${item?.id}`;
|
||||||
case 2:
|
case 2:
|
||||||
return `/public/content/video/detail/${item?.id}`;
|
return `/content/video/detail/${item?.id}`;
|
||||||
case 3:
|
case 3:
|
||||||
return `/public/content/text/detail/${item?.id}`;
|
return `/content/text/detail/${item?.id}`;
|
||||||
case 4:
|
case 4:
|
||||||
return `/public/content/audio/detail/${item?.id}`;
|
return `/content/audio/detail/${item?.id}`;
|
||||||
default:
|
default:
|
||||||
return "#"; // fallback kalau type tidak dikenali
|
return "#"; // fallback kalau type tidak dikenali
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "../ui/card";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listArticles } from "@/service/landing/landing";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
|
|
@ -42,20 +42,71 @@ export default function MediaUpdate() {
|
||||||
async function fetchData(section: "latest" | "popular") {
|
async function fetchData(section: "latest" | "popular") {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await listData(
|
|
||||||
"1",
|
// Use new Articles API
|
||||||
"",
|
const response = await listArticles(
|
||||||
"",
|
1,
|
||||||
20,
|
20,
|
||||||
0,
|
1, // typeId for images
|
||||||
section === "latest" ? "createdAt" : "clickCount",
|
undefined,
|
||||||
"",
|
undefined,
|
||||||
"",
|
section === "latest" ? "createdAt" : "viewCount"
|
||||||
""
|
|
||||||
);
|
);
|
||||||
setDataToRender(res?.data?.data?.content || []);
|
|
||||||
|
console.log("Media Update Articles API response:", response);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
console.error("Articles API failed, falling back to old API");
|
||||||
|
// Fallback to old API
|
||||||
|
const fallbackRes = await listData(
|
||||||
|
"1",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
section === "latest" ? "createdAt" : "clickCount",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
setDataToRender(fallbackRes?.data?.data?.content || []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articlesData = response?.data?.data || [];
|
||||||
|
|
||||||
|
// Transform articles data to match old structure for backward compatibility
|
||||||
|
const transformedData = articlesData.map((article: any) => ({
|
||||||
|
id: article.id,
|
||||||
|
title: article.title,
|
||||||
|
category: article.categoryName || (article.categories && article.categories[0]?.title) || "Tanpa Kategori",
|
||||||
|
createdAt: article.createdAt,
|
||||||
|
smallThumbnailLink: article.thumbnailUrl,
|
||||||
|
label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "",
|
||||||
|
...article
|
||||||
|
}));
|
||||||
|
|
||||||
|
setDataToRender(transformedData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Gagal memuat data:", err);
|
console.error("Gagal memuat data:", err);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackRes = await listData(
|
||||||
|
"1",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
section === "latest" ? "createdAt" : "clickCount",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
setDataToRender(fallbackRes?.data?.data?.content || []);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail } from "@/service/landing/landing";
|
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||||
|
|
||||||
export default function AudioDetail({ id }: { id: string }) {
|
export default function AudioDetail({ id }: { id: string }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
@ -60,14 +60,75 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
const fetchDetail = async () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await getDetail(id);
|
|
||||||
setData(response?.data?.data);
|
// Try new Articles API first
|
||||||
console.log(
|
const response = await getArticleDetail(id);
|
||||||
"doc",
|
console.log("Article Detail API response:", response);
|
||||||
response?.data?.data.files[selectedDoc]?.secondaryUrl
|
|
||||||
);
|
if (response?.error) {
|
||||||
|
console.error("Articles API failed, falling back to old API");
|
||||||
|
// Fallback to old API
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articleData = response?.data?.data;
|
||||||
|
if (articleData) {
|
||||||
|
// Transform article data to match old structure for backward compatibility
|
||||||
|
const transformedData = {
|
||||||
|
id: articleData.id,
|
||||||
|
title: articleData.title,
|
||||||
|
description: articleData.description,
|
||||||
|
createdAt: articleData.createdAt,
|
||||||
|
clickCount: articleData.viewCount,
|
||||||
|
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||||
|
uploadedBy: {
|
||||||
|
publisher: articleData.createdByName || "MABES POLRI"
|
||||||
|
},
|
||||||
|
files: articleData.files?.map((file: any) => ({
|
||||||
|
id: file.id,
|
||||||
|
url: file.file_url,
|
||||||
|
fileName: file.file_name,
|
||||||
|
filePath: file.file_path,
|
||||||
|
fileThumbnail: file.file_thumbnail,
|
||||||
|
fileAlt: file.file_alt,
|
||||||
|
widthPixel: file.width_pixel,
|
||||||
|
heightPixel: file.height_pixel,
|
||||||
|
size: file.size,
|
||||||
|
downloadCount: file.download_count,
|
||||||
|
createdAt: file.created_at,
|
||||||
|
updatedAt: file.updated_at,
|
||||||
|
secondaryUrl: file.file_url, // For audio files, use same URL
|
||||||
|
...file
|
||||||
|
})) || [],
|
||||||
|
...articleData
|
||||||
|
};
|
||||||
|
|
||||||
|
setData(transformedData);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
transformedData.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching detail:", error);
|
console.error("Error fetching detail:", error);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +246,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -238,7 +299,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
<span>SHARE</span>
|
<span>SHARE</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail } from "@/service/landing/landing";
|
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||||
import VideoPlayer from "@/utils/video-player";
|
import VideoPlayer from "@/utils/video-player";
|
||||||
import { toBase64, shimmer } from "@/utils/globals";
|
import { toBase64, shimmer } from "@/utils/globals";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
@ -63,14 +63,75 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
const fetchDetail = async () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await getDetail(id);
|
|
||||||
setData(response?.data?.data);
|
// Try new Articles API first
|
||||||
console.log(
|
const response = await getArticleDetail(id);
|
||||||
"doc",
|
console.log("Article Detail API response:", response);
|
||||||
response?.data?.data.files[selectedDoc]?.secondaryUrl
|
|
||||||
);
|
if (response?.error) {
|
||||||
|
console.error("Articles API failed, falling back to old API");
|
||||||
|
// Fallback to old API
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articleData = response?.data?.data;
|
||||||
|
if (articleData) {
|
||||||
|
// Transform article data to match old structure for backward compatibility
|
||||||
|
const transformedData = {
|
||||||
|
id: articleData.id,
|
||||||
|
title: articleData.title,
|
||||||
|
description: articleData.description,
|
||||||
|
createdAt: articleData.createdAt,
|
||||||
|
clickCount: articleData.viewCount,
|
||||||
|
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||||
|
uploadedBy: {
|
||||||
|
publisher: articleData.createdByName || "MABES POLRI"
|
||||||
|
},
|
||||||
|
files: articleData.files?.map((file: any) => ({
|
||||||
|
id: file.id,
|
||||||
|
url: file.file_url,
|
||||||
|
fileName: file.file_name,
|
||||||
|
filePath: file.file_path,
|
||||||
|
fileThumbnail: file.file_thumbnail,
|
||||||
|
fileAlt: file.file_alt,
|
||||||
|
widthPixel: file.width_pixel,
|
||||||
|
heightPixel: file.height_pixel,
|
||||||
|
size: file.size,
|
||||||
|
downloadCount: file.download_count,
|
||||||
|
createdAt: file.created_at,
|
||||||
|
updatedAt: file.updated_at,
|
||||||
|
secondaryUrl: file.file_url, // For document files, use same URL
|
||||||
|
...file
|
||||||
|
})) || [],
|
||||||
|
...articleData
|
||||||
|
};
|
||||||
|
|
||||||
|
setData(transformedData);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
transformedData.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching detail:", error);
|
console.error("Error fetching detail:", error);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
console.log(
|
||||||
|
"doc",
|
||||||
|
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||||
|
);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +250,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -242,7 +303,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
<span>SHARE</span>
|
<span>SHARE</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail } from "@/service/landing/landing";
|
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||||
import VideoPlayer from "@/utils/video-player";
|
import VideoPlayer from "@/utils/video-player";
|
||||||
import { toBase64, shimmer } from "@/utils/globals";
|
import { toBase64, shimmer } from "@/utils/globals";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
@ -63,10 +63,63 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
const fetchDetail = async () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await getDetail(id);
|
|
||||||
setData(response?.data?.data);
|
// Try new Articles API first
|
||||||
|
const response = await getArticleDetail(id);
|
||||||
|
console.log("Article Detail API response:", response);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
console.error("Articles API failed, falling back to old API");
|
||||||
|
// Fallback to old API
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articleData = response?.data?.data;
|
||||||
|
if (articleData) {
|
||||||
|
// Transform article data to match old structure for backward compatibility
|
||||||
|
const transformedData = {
|
||||||
|
id: articleData.id,
|
||||||
|
title: articleData.title,
|
||||||
|
description: articleData.description,
|
||||||
|
createdAt: articleData.createdAt,
|
||||||
|
clickCount: articleData.viewCount,
|
||||||
|
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||||
|
uploadedBy: {
|
||||||
|
publisher: articleData.createdByName || "MABES POLRI"
|
||||||
|
},
|
||||||
|
files: articleData.files?.map((file: any) => ({
|
||||||
|
id: file.id,
|
||||||
|
url: file.file_url,
|
||||||
|
fileName: file.file_name,
|
||||||
|
filePath: file.file_path,
|
||||||
|
fileThumbnail: file.file_thumbnail,
|
||||||
|
fileAlt: file.file_alt,
|
||||||
|
widthPixel: file.width_pixel,
|
||||||
|
heightPixel: file.height_pixel,
|
||||||
|
size: file.size,
|
||||||
|
downloadCount: file.download_count,
|
||||||
|
createdAt: file.created_at,
|
||||||
|
updatedAt: file.updated_at,
|
||||||
|
...file
|
||||||
|
})) || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("transformedData : ", transformedData.files);
|
||||||
|
|
||||||
|
setData(transformedData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching detail:", error);
|
console.error("Error fetching detail:", error);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +247,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||||
{/* Sidebar actions */}
|
{/* Sidebar actions */}
|
||||||
<div className="hidden md:flex flex-col gap-4 relative z-10">
|
{/* <div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|
@ -229,7 +282,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -250,7 +303,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
COMMENT
|
COMMENT
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
|
|
@ -282,7 +335,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
<span>SHARE</span>
|
<span>SHARE</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail } from "@/service/landing/landing";
|
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||||
import VideoPlayer from "@/utils/video-player";
|
import VideoPlayer from "@/utils/video-player";
|
||||||
|
|
||||||
export default function VideoDetail({ id }: { id: string }) {
|
export default function VideoDetail({ id }: { id: string }) {
|
||||||
|
|
@ -52,10 +52,63 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
const fetchDetail = async () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await getDetail(id);
|
|
||||||
setData(response?.data?.data);
|
// Try new Articles API first
|
||||||
|
const response = await getArticleDetail(id);
|
||||||
|
console.log("Article Detail API response:", response);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
console.error("Articles API failed, falling back to old API");
|
||||||
|
// Fallback to old API
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new API response structure
|
||||||
|
const articleData = response?.data?.data;
|
||||||
|
if (articleData) {
|
||||||
|
// Transform article data to match old structure for backward compatibility
|
||||||
|
const transformedData = {
|
||||||
|
id: articleData.id,
|
||||||
|
title: articleData.title,
|
||||||
|
description: articleData.description,
|
||||||
|
createdAt: articleData.createdAt,
|
||||||
|
clickCount: articleData.viewCount,
|
||||||
|
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||||
|
uploadedBy: {
|
||||||
|
publisher: articleData.createdByName || "MABES POLRI"
|
||||||
|
},
|
||||||
|
files: articleData.files?.map((file: any) => ({
|
||||||
|
id: file.id,
|
||||||
|
url: file.file_url,
|
||||||
|
fileName: file.file_name,
|
||||||
|
filePath: file.file_path,
|
||||||
|
fileThumbnail: file.file_thumbnail,
|
||||||
|
fileAlt: file.file_alt,
|
||||||
|
widthPixel: file.width_pixel,
|
||||||
|
heightPixel: file.height_pixel,
|
||||||
|
size: file.size,
|
||||||
|
downloadCount: file.download_count,
|
||||||
|
createdAt: file.created_at,
|
||||||
|
updatedAt: file.updated_at,
|
||||||
|
thumbnailFileUrl: file.file_thumbnail || articleData.thumbnailUrl,
|
||||||
|
...file
|
||||||
|
})) || [],
|
||||||
|
...articleData
|
||||||
|
};
|
||||||
|
|
||||||
|
setData(transformedData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching detail:", error);
|
console.error("Error fetching detail:", error);
|
||||||
|
// Try fallback to old API if new API fails
|
||||||
|
try {
|
||||||
|
const fallbackResponse = await getDetail(id);
|
||||||
|
setData(fallbackResponse?.data?.data);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +232,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -232,7 +285,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
<span>SHARE</span>
|
<span>SHARE</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/public/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default function PublicationKlFilter({
|
||||||
return (
|
return (
|
||||||
<Card className="overflow-hidden shadow-md w-[315px] h-[417px] p-0 flex flex-col">
|
<Card className="overflow-hidden shadow-md w-[315px] h-[417px] p-0 flex flex-col">
|
||||||
{/* Gambar atas */}
|
{/* Gambar atas */}
|
||||||
<Link href={`/public/content/video/detail/${id}`}>
|
<Link href={`/content/video/detail/${id}`}>
|
||||||
<div className="relative w-full h-[250px]">
|
<div className="relative w-full h-[250px]">
|
||||||
<Image src={image} alt={title} fill className="object-cover" />
|
<Image src={image} alt={title} fill className="object-cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
domains: ["kontenhumas.com"],
|
domains: ["kontenhumas.com", "dev.mikulnews.com"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,31 @@ export async function listData(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New Articles API for public/landing usage
|
||||||
|
export async function listArticles(
|
||||||
|
page = 1,
|
||||||
|
totalPage = 10,
|
||||||
|
typeId?: number,
|
||||||
|
search?: string,
|
||||||
|
categoryId?: string,
|
||||||
|
sortBy = "createdAt"
|
||||||
|
) {
|
||||||
|
let url = `articles?page=${page}&totalPage=${totalPage}`;
|
||||||
|
|
||||||
|
if (typeId !== undefined) url += `&typeId=${typeId}`;
|
||||||
|
if (search) url += `&title=${encodeURIComponent(search)}`;
|
||||||
|
if (categoryId) url += `&categoryId=${categoryId}`;
|
||||||
|
// if (sortBy) url += `&sortBy=${sortBy}`;
|
||||||
|
|
||||||
|
return await httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Article Detail API for public/landing usage
|
||||||
|
export async function getArticleDetail(id: string | number) {
|
||||||
|
const url = `articles/${id}`;
|
||||||
|
return await httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
export async function listDataRegional(
|
export async function listDataRegional(
|
||||||
type: string,
|
type: string,
|
||||||
search: string,
|
search: string,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue