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 { getCsrfToken } from "@/service/auth";
|
||||
import {
|
||||
listEnableCategory,
|
||||
getTagsBySubCategoryId,
|
||||
createMedia,
|
||||
createArticle,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listArticleCategories,
|
||||
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 {
|
||||
generateDataArticle,
|
||||
|
|
@ -54,6 +61,7 @@ import {
|
|||
getGenerateKeywords,
|
||||
getGenerateTitle,
|
||||
} from "@/service/content/ai";
|
||||
import Link from "next/link";
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
|
|
@ -188,27 +196,30 @@ export default function FormVideo() {
|
|||
});
|
||||
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
title: z.string().min(1, { message: "titleRequired" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
||||
tags: z
|
||||
.array(z.string().min(1))
|
||||
.min(1, { message: "Minimal 1 tag diperlukan" }),
|
||||
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||
files: z
|
||||
.array(z.any())
|
||||
.min(1, { message: "File video harus dipilih" })
|
||||
.min(1, { message: "Minimal 1 file harus diunggah." })
|
||||
.refine(
|
||||
(files) =>
|
||||
files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)),
|
||||
{ message: "File harus berformat mp4 atau mov" }
|
||||
)
|
||||
.refine(
|
||||
(files) => files.every((file: File) => file.size <= MAX_FILE_SIZE),
|
||||
{ message: "Ukuran file maksimal 100 MB" }
|
||||
files.every(
|
||||
(file: File) =>
|
||||
["video/mp4", "video/mov", "video/avi"].includes(file.type) &&
|
||||
file.size <= 100 * 1024 * 1024
|
||||
),
|
||||
{
|
||||
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
|
||||
.array(z.string())
|
||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||
|
|
@ -227,8 +238,9 @@ export default function FormVideo() {
|
|||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
category: "",
|
||||
creatorName: "",
|
||||
files: [],
|
||||
categoryId: "",
|
||||
tags: [],
|
||||
publishedFor: [],
|
||||
},
|
||||
|
|
@ -463,11 +475,30 @@ export default function FormVideo() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
// 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 || [];
|
||||
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);
|
||||
console.log("data category", resCategory);
|
||||
console.log("Article categories loaded:", resCategory);
|
||||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
|
|
@ -475,7 +506,6 @@ export default function FormVideo() {
|
|||
);
|
||||
|
||||
if (findCategory) {
|
||||
// setValue("categoryId", findCategory.id);
|
||||
setSelectedCategory(findCategory.id);
|
||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||
setTags(response?.data?.data);
|
||||
|
|
@ -483,6 +513,14 @@ export default function FormVideo() {
|
|||
}
|
||||
} catch (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,40 +616,99 @@ export default function FormVideo() {
|
|||
}
|
||||
|
||||
if (id == undefined) {
|
||||
const response = await createMedia(requestData);
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
// New Articles API request data structure
|
||||
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) {
|
||||
MySwal.fire("Error", response?.message, "error");
|
||||
return;
|
||||
}
|
||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
||||
id = response?.data?.data;
|
||||
|
||||
if (thumbnail) {
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("file", thumbnail);
|
||||
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||
if (responseThumbnail?.error) {
|
||||
error(responseThumbnail.message);
|
||||
MySwal.fire("Error", response.message || "Failed to create article", "error");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const progressInfoArr = [];
|
||||
for (const item of files) {
|
||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||
}
|
||||
progressInfo = progressInfoArr;
|
||||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
// Get the article ID from the new API response
|
||||
const articleId = response?.data?.data?.id;
|
||||
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||
id = articleId;
|
||||
|
||||
// Upload files using new article-files API
|
||||
const formData = new FormData();
|
||||
|
||||
// Add all files to FormData
|
||||
files.forEach((file, index) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
|
||||
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/video");
|
||||
});
|
||||
|
||||
Cookies.remove("idCreate");
|
||||
return;
|
||||
}
|
||||
|
||||
Cookies.remove("idCreate");
|
||||
};
|
||||
|
||||
|
|
@ -885,14 +982,15 @@ export default function FormVideo() {
|
|||
<Label>Category</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="category"
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<div className="w-full">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(id) => {
|
||||
field.onChange(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id); // tetap set ini kalau mau
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setSelectedCategory(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
|
|
@ -909,14 +1007,16 @@ export default function FormVideo() {
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.category && (
|
||||
<p className="text-red-500 text-sm">
|
||||
{errors.category.message}
|
||||
|
||||
{errors.categoryId && (
|
||||
<p className="text-sm text-red-500 mt-1">
|
||||
{errors.categoryId.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Ai Assistance</Label>
|
||||
|
|
|
|||
|
|
@ -32,9 +32,14 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
createArticle,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listArticleCategories,
|
||||
uploadThumbnail,
|
||||
uploadArticleFiles,
|
||||
uploadArticleThumbnail,
|
||||
CreateArticleData,
|
||||
} from "@/service/content/content";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
|
|
@ -54,6 +59,8 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
|||
import dynamic from "next/dynamic";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { useParams } from "next/navigation";
|
||||
import { request } from "http";
|
||||
import { toast } from "sonner";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import Link from "next/link";
|
||||
|
||||
|
|
@ -127,7 +134,7 @@ export default function FormAudio() {
|
|||
polres: false,
|
||||
});
|
||||
|
||||
let fileTypeId = "4";
|
||||
let fileTypeId = "3";
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
|
|
@ -197,18 +204,30 @@ export default function FormAudio() {
|
|||
};
|
||||
|
||||
const audioSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
title: z.string().min(1, { message: "titleRequired" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||
category: z.string().min(1, { message: "Category diperlukan" }),
|
||||
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||
files: z
|
||||
.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
|
||||
.array(z.string().min(1))
|
||||
.min(1, { message: "Wajib isi minimal 1 tag" }),
|
||||
.array(z.string())
|
||||
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
||||
publishedFor: z
|
||||
.array(z.string())
|
||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||
|
|
@ -227,7 +246,9 @@ export default function FormAudio() {
|
|||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
category: "",
|
||||
creatorName: "",
|
||||
files: [],
|
||||
categoryId: "",
|
||||
tags: [],
|
||||
publishedFor: [],
|
||||
},
|
||||
|
|
@ -456,11 +477,30 @@ export default function FormAudio() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
// 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 || [];
|
||||
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);
|
||||
console.log("data category", resCategory);
|
||||
console.log("Article categories loaded:", resCategory);
|
||||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
|
|
@ -468,7 +508,6 @@ export default function FormAudio() {
|
|||
);
|
||||
|
||||
if (findCategory) {
|
||||
// setValue("categoryId", findCategory.id);
|
||||
setSelectedCategory(findCategory.id);
|
||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||
setTags(response?.data?.data);
|
||||
|
|
@ -476,6 +515,14 @@ export default function FormAudio() {
|
|||
}
|
||||
} catch (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) {
|
||||
const response = await createMedia(requestData);
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
// New Articles API request data structure
|
||||
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) {
|
||||
MySwal.fire("Error", response?.message, "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);
|
||||
MySwal.fire("Error", response.message || "Failed to create article", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the article ID from the new API response
|
||||
const articleId = response?.data?.data?.id;
|
||||
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||
id = articleId;
|
||||
|
||||
// Upload files using new article-files API
|
||||
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;
|
||||
}
|
||||
|
||||
const progressInfoArr = [];
|
||||
for (const item of files) {
|
||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||
}
|
||||
progressInfo = progressInfoArr;
|
||||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
console.log("Files uploaded successfully:", uploadResponse);
|
||||
|
||||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
// 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;
|
||||
}
|
||||
|
||||
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
|
||||
Cookies.remove("idCreate");
|
||||
};
|
||||
|
||||
const onSubmit = (data: AudioSchema) => {
|
||||
|
|
@ -867,17 +971,16 @@ export default function FormAudio() {
|
|||
<div className="py-3 w-full space-y-2">
|
||||
<Label>Category</Label>
|
||||
<Controller
|
||||
name="category"
|
||||
control={control}
|
||||
rules={{ required: "Category is required" }}
|
||||
render={({ field, fieldState }) => (
|
||||
<>
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<div className="w-full">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(id) => {
|
||||
field.onChange(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id);
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setSelectedCategory(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
|
|
@ -894,12 +997,13 @@ export default function FormAudio() {
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{fieldState.error && (
|
||||
<p className="text-sm text-red-500">
|
||||
{fieldState.error.message}
|
||||
|
||||
{errors.categoryId && (
|
||||
<p className="text-sm text-red-500 mt-1">
|
||||
{errors.categoryId.message}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,9 +30,14 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
createArticle,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listArticleCategories,
|
||||
uploadThumbnail,
|
||||
uploadArticleFiles,
|
||||
uploadArticleThumbnail,
|
||||
CreateArticleData,
|
||||
} from "@/service/content/content";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
|
|
@ -50,6 +55,8 @@ import Image from "next/image";
|
|||
import { error, loading } from "@/config/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { request } from "http";
|
||||
import { toast } from "sonner";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import Link from "next/link";
|
||||
|
||||
|
|
@ -125,7 +132,7 @@ export default function FormTeks() {
|
|||
polres: false,
|
||||
});
|
||||
|
||||
let fileTypeId = "3";
|
||||
let fileTypeId = "2";
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
|
|
@ -178,38 +185,34 @@ export default function FormTeks() {
|
|||
});
|
||||
|
||||
const teksSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
||||
tags: z
|
||||
.array(z.string())
|
||||
.min(1, { message: "Minimal 1 tag harus ditambahkan." }),
|
||||
title: z.string().min(1, { message: "titleRequired" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
creatorName: z.string().min(1, { message: "creatorRequired" }),
|
||||
files: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
size: z
|
||||
.number()
|
||||
.max(20 * 1024 * 1024, { message: "Max file size 20 MB" }),
|
||||
})
|
||||
.array(z.any())
|
||||
.min(1, { message: "Minimal 1 file harus diunggah." })
|
||||
.refine(
|
||||
(file) =>
|
||||
(files) =>
|
||||
files.every(
|
||||
(file: File) =>
|
||||
[
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
].includes(file.type),
|
||||
{ message: "Format file tidak didukung" }
|
||||
)
|
||||
)
|
||||
.min(1, { message: "File wajib diunggah" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
"text/plain",
|
||||
].includes(file.type) && file.size <= 100 * 1024 * 1024
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Hanya file .pdf, .doc, .docx, .txt, 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
|
||||
.array(z.string())
|
||||
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
|
||||
|
|
@ -226,13 +229,13 @@ export default function FormTeks() {
|
|||
resolver: zodResolver(teksSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
creatorName: "",
|
||||
category: "",
|
||||
tags: [],
|
||||
files: [],
|
||||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
creatorName: "",
|
||||
files: [],
|
||||
categoryId: "",
|
||||
tags: [],
|
||||
publishedFor: [],
|
||||
},
|
||||
});
|
||||
|
|
@ -470,11 +473,30 @@ export default function FormTeks() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
// 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 || [];
|
||||
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);
|
||||
console.log("data category", resCategory);
|
||||
console.log("Article categories loaded:", resCategory);
|
||||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
|
|
@ -482,14 +504,21 @@ export default function FormTeks() {
|
|||
);
|
||||
|
||||
if (findCategory) {
|
||||
// setValue("categoryId", findCategory.id);
|
||||
setSelectedCategory(findCategory.id); // Set the selected category
|
||||
setSelectedCategory(findCategory.id);
|
||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||
setTags(response?.data?.data);
|
||||
}
|
||||
}
|
||||
} catch (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,38 +618,99 @@ export default function FormTeks() {
|
|||
}
|
||||
|
||||
if (id == undefined) {
|
||||
const response = await createMedia(requestData);
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
// New Articles API request data structure
|
||||
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 });
|
||||
id = response?.data?.data;
|
||||
const formMedia = new FormData();
|
||||
const thumbnail = files[0];
|
||||
formMedia.append("file", thumbnail);
|
||||
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||
if (responseThumbnail?.error == true) {
|
||||
error(responseThumbnail?.message);
|
||||
// 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");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const progressInfoArr = files.map((item) => ({
|
||||
percentage: 0,
|
||||
fileName: item.name,
|
||||
}));
|
||||
progressInfo = progressInfoArr;
|
||||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
// Get the article ID from the new API response
|
||||
const articleId = response?.data?.data?.id;
|
||||
Cookies.set("idCreate", articleId, { expires: 1 });
|
||||
id = articleId;
|
||||
|
||||
// Upload files using new article-files API
|
||||
const formData = new FormData();
|
||||
|
||||
// Add all files to FormData
|
||||
files.forEach((file, index) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
|
||||
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");
|
||||
};
|
||||
|
||||
|
|
@ -848,7 +938,7 @@ export default function FormTeks() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -875,13 +965,15 @@ export default function FormTeks() {
|
|||
<Label>Category</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="category"
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<div className="w-full">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(val) => {
|
||||
field.onChange(val);
|
||||
setSelectedCategory(val);
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setSelectedCategory(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
|
|
@ -898,14 +990,16 @@ export default function FormTeks() {
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.category?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.category.message}
|
||||
|
||||
{errors.categoryId && (
|
||||
<p className="text-sm text-red-500 mt-1">
|
||||
{errors.categoryId.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Ai Assistance</Label>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
|||
import {
|
||||
getListContent,
|
||||
listData,
|
||||
listArticles,
|
||||
listStaticBanner,
|
||||
} from "@/service/landing/landing";
|
||||
import { data } from "framer-motion/client";
|
||||
|
|
@ -17,10 +18,14 @@ export default function Header() {
|
|||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// const request = {
|
||||
// group: "mabes",
|
||||
// };
|
||||
const response = await listData(
|
||||
// Use new Articles API
|
||||
const response = await listArticles(1, 5, undefined, undefined, undefined, "createdAt");
|
||||
console.log("Articles API response:", response);
|
||||
|
||||
if (response?.error) {
|
||||
console.error("Articles API failed, falling back to old API");
|
||||
// Fallback to old API
|
||||
const fallbackResponse = await listData(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
|
|
@ -31,11 +36,48 @@ export default function Header() {
|
|||
"",
|
||||
""
|
||||
);
|
||||
const content = response?.data?.data?.content || [];
|
||||
console.log("data", 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) {
|
||||
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 = () => {
|
||||
switch (item?.fileTypeId) {
|
||||
case 1:
|
||||
return `/public/content/image/detail/${item?.id}`;
|
||||
return `/content/image/detail/${item?.id}`;
|
||||
case 2:
|
||||
return `/public/content/video/detail/${item?.id}`;
|
||||
return `/content/video/detail/${item?.id}`;
|
||||
case 3:
|
||||
return `/public/content/text/detail/${item?.id}`;
|
||||
return `/content/text/detail/${item?.id}`;
|
||||
case 4:
|
||||
return `/public/content/audio/detail/${item?.id}`;
|
||||
return `/content/audio/detail/${item?.id}`;
|
||||
default:
|
||||
return "#"; // fallback kalau type tidak dikenali
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||
import { Card } from "../ui/card";
|
||||
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/css";
|
||||
import "swiper/css/navigation";
|
||||
|
|
@ -42,7 +42,23 @@ export default function MediaUpdate() {
|
|||
async function fetchData(section: "latest" | "popular") {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await listData(
|
||||
|
||||
// Use new Articles API
|
||||
const response = await listArticles(
|
||||
1,
|
||||
20,
|
||||
1, // typeId for images
|
||||
undefined,
|
||||
undefined,
|
||||
section === "latest" ? "createdAt" : "viewCount"
|
||||
);
|
||||
|
||||
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",
|
||||
"",
|
||||
"",
|
||||
|
|
@ -53,9 +69,44 @@ export default function MediaUpdate() {
|
|||
"",
|
||||
""
|
||||
);
|
||||
setDataToRender(res?.data?.data?.content || []);
|
||||
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) {
|
||||
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 {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
|
||||
export default function AudioDetail({ id }: { id: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
|
@ -60,14 +60,75 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
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);
|
||||
console.log(
|
||||
"doc",
|
||||
response?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
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) {
|
||||
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 {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -185,7 +246,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
@ -238,7 +299,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
<span>SHARE</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
import { toBase64, shimmer } from "@/utils/globals";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
|
@ -63,14 +63,75 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
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);
|
||||
console.log(
|
||||
"doc",
|
||||
response?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
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) {
|
||||
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 {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -189,7 +250,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
@ -242,7 +303,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
<span>SHARE</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
import { toBase64, shimmer } from "@/utils/globals";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
|
@ -63,10 +63,63 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
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) {
|
||||
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 {
|
||||
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">
|
||||
{/* 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">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
|
|
@ -229,7 +282,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
@ -250,7 +303,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
COMMENT
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 space-y-4">
|
||||
|
|
@ -282,7 +335,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
<span>SHARE</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
|
||||
export default function VideoDetail({ id }: { id: string }) {
|
||||
|
|
@ -52,10 +52,63 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
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) {
|
||||
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 {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -179,7 +232,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
@ -232,7 +285,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
<span>SHARE</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/public/content/video/comment/${id}`}>
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function PublicationKlFilter({
|
|||
return (
|
||||
<Card className="overflow-hidden shadow-md w-[315px] h-[417px] p-0 flex flex-col">
|
||||
{/* Gambar atas */}
|
||||
<Link href={`/public/content/video/detail/${id}`}>
|
||||
<Link href={`/content/video/detail/${id}`}>
|
||||
<div className="relative w-full h-[250px]">
|
||||
<Image src={image} alt={title} fill className="object-cover" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { NextConfig } from "next";
|
|||
|
||||
const nextConfig = {
|
||||
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(
|
||||
type: string,
|
||||
search: string,
|
||||
|
|
|
|||
Loading…
Reference in New Issue