fix: add button translate in image upload and edit admin, change url api in all filter content

This commit is contained in:
Sabda Yagra 2025-10-15 10:57:41 +07:00
parent 838b492768
commit 92811802cc
11 changed files with 726 additions and 319 deletions

View File

@ -28,21 +28,10 @@ import {
import { useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./column";
import { listEnableCategory } from "@/service/content/content";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link, useRouter } from "@/i18n/routing";
import { NewCampaignIcon } from "@/components/icon";
import { getCategories } from "@/service/settings/settings";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import CreateCategoryModal from "./create";
import { useTranslations } from "next-intl";
import {

View File

@ -593,6 +593,116 @@ const FilterPage = () => {
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
<label
className="inline-flex items-center"
htmlFor={`${category.id}`}
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label>
</li>
))}
</ul>
{/* ⬇️ Pagination kategori (rata sejajar) */}
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
{/* Tombol Prev */}
<button
onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1))
}
disabled={categoryPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
/>
</svg>
</button>
{(() => {
const maxVisible = 4;
let startPage = Math.max(
1,
Math.min(
categoryPage - Math.floor(maxVisible / 2),
categoryTotalPages - maxVisible + 1
)
);
const endPage = Math.min(
categoryTotalPages,
startPage + maxVisible - 1
);
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
return visiblePages.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCategoryPage(pageNum)}
className={`px-3 py-1 border rounded text-sm transition-colors ${
categoryPage === pageNum
? "bg-[#bb3523] text-white"
: "bg-white dark:bg-gray-800"
}`}
>
{pageNum}
</button>
));
})()}
{/* Tombol Next */}
<button
onClick={() =>
setCategoryPage((prev) =>
Math.min(prev + 1, categoryTotalPages)
)
}
disabled={categoryPage === categoryTotalPages}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
/>
</svg>
</button>
</div>
</div>
{/* <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories?.map((category: any) => (
<li key={category?.id}>
@ -670,7 +780,7 @@ const FilterPage = () => {
</button>
</div>
</ul>
</div>
</div> */}
{/* Garis */}
<div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */}

View File

@ -609,6 +609,116 @@ const FilterPage = () => {
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
<label
className="inline-flex items-center"
htmlFor={`${category.id}`}
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label>
</li>
))}
</ul>
{/* ⬇️ Pagination kategori (rata sejajar) */}
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
{/* Tombol Prev */}
<button
onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1))
}
disabled={categoryPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
/>
</svg>
</button>
{(() => {
const maxVisible = 4;
let startPage = Math.max(
1,
Math.min(
categoryPage - Math.floor(maxVisible / 2),
categoryTotalPages - maxVisible + 1
)
);
const endPage = Math.min(
categoryTotalPages,
startPage + maxVisible - 1
);
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
return visiblePages.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCategoryPage(pageNum)}
className={`px-3 py-1 border rounded text-sm transition-colors ${
categoryPage === pageNum
? "bg-[#bb3523] text-white"
: "bg-white dark:bg-gray-800"
}`}
>
{pageNum}
</button>
));
})()}
{/* Tombol Next */}
<button
onClick={() =>
setCategoryPage((prev) =>
Math.min(prev + 1, categoryTotalPages)
)
}
disabled={categoryPage === categoryTotalPages}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
/>
</svg>
</button>
</div>
</div>
{/* <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
@ -686,7 +796,7 @@ const FilterPage = () => {
</button>
</div>
</ul>
</div>
</div> */}
{/* Garis */}
<div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */}

View File

@ -627,6 +627,7 @@ const FilterPage = () => {
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
@ -648,18 +649,22 @@ const FilterPage = () => {
</label>
</li>
))}
<div className="mt-4 flex gap-2 justify-center items-center">
</ul>
{/* ⬇️ Pagination kategori (rata sejajar) */}
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
{/* Tombol Prev */}
<button
onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1))
}
disabled={categoryPage === 1}
className="px-3 py-1 border rounded disabled:opacity-50"
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
@ -669,20 +674,41 @@ const FilterPage = () => {
</svg>
</button>
{Array.from({ length: categoryTotalPages }, (_, i) => (
{(() => {
const maxVisible = 4;
let startPage = Math.max(
1,
Math.min(
categoryPage - Math.floor(maxVisible / 2),
categoryTotalPages - maxVisible + 1
)
);
const endPage = Math.min(
categoryTotalPages,
startPage + maxVisible - 1
);
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
return visiblePages.map((pageNum) => (
<button
key={i}
onClick={() => setCategoryPage(i + 1)}
className={`px-3 py-1 border rounded ${
categoryPage === i + 1
key={pageNum}
onClick={() => setCategoryPage(pageNum)}
className={`px-3 py-1 border rounded text-sm transition-colors ${
categoryPage === pageNum
? "bg-[#bb3523] text-white"
: ""
: "bg-white dark:bg-gray-800"
}`}
>
{i + 1}
{pageNum}
</button>
))}
));
})()}
{/* Tombol Next */}
<button
onClick={() =>
setCategoryPage((prev) =>
@ -690,12 +716,12 @@ const FilterPage = () => {
)
}
disabled={categoryPage === categoryTotalPages}
className="px-3 py-1 border rounded disabled:opacity-50"
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
@ -705,8 +731,8 @@ const FilterPage = () => {
</svg>
</button>
</div>
</ul>
</div>
{/* Garis */}
<div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */}

View File

@ -610,6 +610,116 @@ const FilterPage = () => {
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
<label
className="inline-flex items-center"
htmlFor={`${category.id}`}
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label>
</li>
))}
</ul>
{/* ⬇️ Pagination kategori (rata sejajar) */}
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
{/* Tombol Prev */}
<button
onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1))
}
disabled={categoryPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
/>
</svg>
</button>
{(() => {
const maxVisible = 4;
let startPage = Math.max(
1,
Math.min(
categoryPage - Math.floor(maxVisible / 2),
categoryTotalPages - maxVisible + 1
)
);
const endPage = Math.min(
categoryTotalPages,
startPage + maxVisible - 1
);
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
return visiblePages.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCategoryPage(pageNum)}
className={`px-3 py-1 border rounded text-sm transition-colors ${
categoryPage === pageNum
? "bg-[#bb3523] text-white"
: "bg-white dark:bg-gray-800"
}`}
>
{pageNum}
</button>
));
})()}
{/* Tombol Next */}
<button
onClick={() =>
setCategoryPage((prev) =>
Math.min(prev + 1, categoryTotalPages)
)
}
disabled={categoryPage === categoryTotalPages}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
/>
</svg>
</button>
</div>
</div>
{/* <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories", { defaultValue: "Categories" })}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
@ -687,7 +797,7 @@ const FilterPage = () => {
</button>
</div>
</ul>
</div>
</div> */}
{/* Garis */}
<div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */}

View File

@ -958,7 +958,7 @@ export default function FormImageDetail() {
}
// Set the selected target to the category ID from details
setSelectedTarget(String(details.category.id));
setSelectedTarget(String(details?.category?.id));
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>

View File

@ -152,6 +152,10 @@ export default function FormImage() {
const [translatedContent, setTranslatedContent] = React.useState("");
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
// 🔹 state untuk translate judul
const [translatedTitle, setTranslatedTitle] = useState("");
const [isLoadingTranslateTitle, setIsLoadingTranslateTitle] = useState(false);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
@ -211,6 +215,7 @@ export default function FormImage() {
title: z.string().min(1, { message: t("titleRequired") }),
description: z.string().optional(),
descriptionOri: z.string().optional(),
htmlDescription: z.string().optional(),
rewriteDescription: z.string().optional(),
creatorName: z.string().min(1, { message: t("creatorRequired") }),
files: z
@ -555,48 +560,53 @@ export default function FormImage() {
}
const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title;
// const finalTitle = isSwitchOn ? title : data.title;
let finalTitle = isSwitchOn ? title : data.title;
// ✅ Jika sudah translate judul → kirim versi English
if (translatedTitle && translatedTitle.trim() !== "") {
finalTitle = translatedTitle;
console.log("📤 Upload Title versi Inggris (translate)");
}
// pilih description dasar
let finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
// let finalDescription = isSwitchOn
// ? data.description
// : selectedFileType === "rewrite"
// ? data.rewriteDescription
// : data.descriptionOri;
// 👉 tempelkan hasil translate ke field form agar ikut terkirim
// if (translatedContent) {
// data.descriptionOri = translatedContent; // versi Inggris yang dikirim
// } else {
// data.descriptionOri = getValues("descriptionOri"); // fallback IDN
// }
// ✅ Tentukan deskripsi final yang akan dikirim
let finalDescription = "";
if (translatedContent && translatedContent.trim() !== "") {
// jika user sudah translate → kirim versi Inggris
finalDescription = translatedContent;
console.log("📤 Upload versi Inggris (translate)");
} else if (data.rewriteDescription && selectedFileType === "rewrite") {
finalDescription = data.rewriteDescription;
console.log("📤 Upload versi rewrite");
} else {
// fallback: gunakan versi Indonesia original
finalDescription = data.descriptionOri ?? "";
console.log("📤 Upload versi Indonesia (original)");
}
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
}
// 👉 tempelkan hasil translate ke field form agar ikut terkirim
if (translatedContent) {
data.descriptionOri = translatedContent;
console.log(
"🌍 Translate dimasukkan ke descriptionOri:",
translatedContent
);
}
let requestData: {
title: string;
description: string;
htmlDescription: string;
fileTypeId: string;
categoryId: any;
subCategoryId: any;
uploadedBy: string;
statusId: string;
publishedFor: string;
creatorName: string;
tags: string;
isYoutube: boolean;
isInternationalMedia: boolean;
attachFromScheduleId?: number;
} = {
...data,
let requestData: any = {
title: finalTitle,
description: htmlToString(finalDescription), // plain text
description: htmlToString(finalDescription), // versi plain text
htmlDescription: finalDescription, // versi HTML
fileTypeId,
categoryId: selectedCategory,
@ -605,11 +615,43 @@ export default function FormImage() {
statusId: "1",
publishedFor: publishedFor.join(","),
creatorName: data.creatorName,
tags: finalTags,
tags: tags.join(", "),
isYoutube: false,
isInternationalMedia: false,
};
// let requestData: {
// title: string;
// description: string;
// htmlDescription: string;
// fileTypeId: string;
// categoryId: any;
// subCategoryId: any;
// uploadedBy: string;
// statusId: string;
// publishedFor: string;
// creatorName: string;
// tags: string;
// isYoutube: boolean;
// isInternationalMedia: boolean;
// attachFromScheduleId?: number;
// } = {
// ...data,
// title: finalTitle,
// description: htmlToString(finalDescription), // plain text
// htmlDescription: finalDescription, // versi HTML
// fileTypeId,
// categoryId: selectedCategory,
// subCategoryId: selectedCategory,
// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
// statusId: "1",
// publishedFor: publishedFor.join(","),
// creatorName: data.creatorName,
// tags: finalTags,
// isYoutube: false,
// isInternationalMedia: false,
// };
let id = Cookies.get("idCreate");
if (scheduleId !== undefined) {
@ -881,8 +923,84 @@ export default function FormImage() {
{t("form-image", { defaultValue: "Form Image" })}
</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>{t("title", { defaultValue: "Title" })}</Label>
{roleId === "14" && (
<button
type="button"
onClick={async () => {
try {
loading();
setIsLoadingTranslateTitle(true);
const res = await translateText({
text: getValues("title"),
sourceLang: "ID",
targetLang: "EN",
});
if (!res.error) {
const resultText =
res?.data?.data?.translations?.[0]?.text || "";
setTranslatedTitle(resultText);
}
} catch (err) {
console.error("Translate title gagal:", err);
} finally {
close();
setIsLoadingTranslateTitle(false);
}
}}
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
>
{isLoadingTranslateTitle
? "Translating..."
: "Translate to English"}
</button>
)}
</div>
{/* Title Indonesia */}
<div className="mt-3">
<Label className="text-sm font-semibold">
Indonesian Title
</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Masukkan Judul Bahasa Indonesia"
/>
)}
/>
</div>
{/* Title English (hanya muncul setelah translate) */}
{translatedTitle && (
<div className="mt-4">
<Label className="text-sm font-semibold">
English Title
</Label>
<Input
size="md"
type="text"
value={translatedTitle}
onChange={(e) => setTranslatedTitle(e.target.value)}
placeholder="English version"
/>
</div>
)}
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
{/* <div className="space-y-2 py-3">
<Label>{t("title", { defaultValue: "Title" })}</Label>
<Controller
control={control}
@ -902,7 +1020,7 @@ export default function FormImage() {
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
</div> */}
{/* <div className="flex items-center">
<div className="py-3 space-y-2 w-full">
@ -1233,18 +1351,12 @@ export default function FormImage() {
<RadioGroup
onValueChange={(value) => setSelectedFileType(value)}
value={selectedFileType}
className=" grid-cols-1"
className="grid-cols-1"
>
<div className="">
<RadioGroupItem value="original" id="original-file" />
<Label htmlFor="original-file">
Select Original Description
</Label>
</div>
{/* HAPUS radio, ganti jadi preview side-by-side */}
<div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>
<Label className="text-[15px]">
{t("description", { defaultValue: "Description" })}
</Label>
@ -1265,7 +1377,6 @@ export default function FormImage() {
const resultText =
res?.data?.data?.translations?.[0]?.text ||
"";
setTranslatedContent(resultText);
}
} catch (err) {
@ -1284,23 +1395,12 @@ export default function FormImage() {
</button>
)}
</div>
{/* Pilihan bahasa untuk posting */}
{roleId === "14" && (
<div className="flex items-center gap-4 mb-2">
<label className="flex items-center gap-2">
<input
type="radio"
value="id"
checked={selectedLang === "id"}
onChange={() => setSelectedLang("id")}
/>
<span>Gunakan Bahasa Indonesia</span>
</label>
</div>
)}
{/* Editor Bahasa Indonesia */}
{/* Editor side-by-side */}
<div className="flex flex-col gap-3 mt-3">
<div className="mt-3">
<Label className="text-sm font-semibold">
Indonesian Version
</Label>
<Controller
control={control}
name="descriptionOri"
@ -1311,129 +1411,29 @@ export default function FormImage() {
/>
)}
/>
{/* Editor Bahasa Inggris */}
{translatedContent && (
<div className="mt-4">
<div className="flex flex-col">
<Label className="text-[15px]">
English Version
</Label>{" "}
<label className="flex items-center gap-2">
<input
type="radio"
value="en"
checked={selectedLang === "en"}
onChange={() => setSelectedLang("en")}
disabled={!translatedContent} // kalau belum translate, disable
/>
<span>Gunakan Bahasa Inggris</span>
</label>
</div>
{translatedContent && (
<div className="mt-5">
<Label className="text-sm font-semibold">
English Version
</Label>
<CustomEditor
onChange={(val: any) => setTranslatedContent(val)}
initialData={translatedContent}
/>
</div>
)}
</div>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
{/* <div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>
{t("description", { defaultValue: "Description" })}
</Label>
{roleId === "14" && (
<button
type="button"
onClick={async () => {
try {
loading();
setIsLoadingTranslate(true);
const res = await translateText({
text: getValues("descriptionOri"),
sourceLang: "ID",
targetLang: "EN",
});
console.log("RRRR", res);
if (!res.error) {
// setLocalContent(res.data.translatedText);
setValue(
"descriptionOri",
res?.data?.data?.translations[0]?.text || ""
);
// setEditorContent(res.data.translatedText);
}
} catch (err) {
close();
console.error("Translate gagal:", err);
} finally {
close();
setIsLoadingTranslate(false);
}
}}
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
>
{isLoadingTranslate
? "Translating..."
: "Translate to English"}
</button>
)}
</div>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(val: any) => {
onChange(val);
// setLocalContent(val);
// setEditorContent(val);
}}
initialData={value}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
<p className="text-sm font-semibold mt-3">
Content Rewrite
</p>
)}
</div> */}
{/* <div className="py-3 space-y-2">
<Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={value}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div> */}
<p className="text-sm font-semibold">Content Rewrite</p>
<div className="my-2">
<Button
size="sm"
@ -1444,6 +1444,7 @@ export default function FormImage() {
Content Rewrite
</Button>
</div>
{showRewriteEditor && (
<div>
{isGeneratedArticle && (
@ -1464,12 +1465,6 @@ export default function FormImage() {
))}
</div>
)}
<div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file">
Select Description Rewrite
</Label>
</div>
<div className="py-3 space-y-2">
<Label>
{t("file-rewrite", {
@ -1503,6 +1498,7 @@ export default function FormImage() {
</RadioGroup>
</>
)}
<div className="py-3 space-y-2">
<Label>
{t("select-file", { defaultValue: "Select File" })}

View File

@ -24,9 +24,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
@ -39,7 +36,6 @@ import {
uploadThumbnail,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { CloudUpload, MailIcon } from "lucide-react";
import dynamic from "next/dynamic";
import { useDropzone } from "react-dropzone";
@ -174,6 +170,10 @@ export default function FormImageUpdate() {
const [translatedContent, setTranslatedContent] = React.useState("");
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
// 🔹 State untuk translate judul
const [translatedTitle, setTranslatedTitle] = useState("");
const [isLoadingTranslateTitle, setIsLoadingTranslateTitle] = useState(false);
const handleThumbnailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
@ -983,16 +983,22 @@ export default function FormImageUpdate() {
loading();
const finalTags = tags.join(", ");
// ✅ tentukan isi description sesuai pilihan bahasa
const descFinal =
selectedLang === "en" && translatedContent
? translatedContent
: data.description;
// const descFinal =
// selectedLang === "en" && translatedContent
// ? translatedContent
// : data.description;
const descFinal = translatedContent || data.description;
const finalTitle =
translatedTitle && translatedTitle.trim() !== ""
? translatedTitle
: data.title;
const requestData = {
...data,
id: detail?.id,
title: data.title,
// title: data.title,
title: finalTitle,
description: htmlToString(descFinal), // versi plain text
htmlDescription: descFinal, // versi HTML
fileTypeId,
@ -1560,8 +1566,88 @@ export default function FormImageUpdate() {
{t("form-image", { defaultValue: "Form Image" })}
</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
{/* Input Title dengan translate */}
<div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>{t("title", { defaultValue: "Title" })}</Label>
{roleId === "14" && (
<button
type="button"
onClick={async () => {
try {
loading();
setIsLoadingTranslateTitle(true);
const res = await translateText({
text: getValues("title"),
sourceLang: "ID",
targetLang: "EN",
});
if (!res.error) {
const resultText =
res?.data?.data?.translations?.[0]?.text || "";
setTranslatedTitle(resultText);
}
} catch (err) {
console.error("Translate title gagal:", err);
} finally {
close();
setIsLoadingTranslateTitle(false);
}
}}
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
>
{isLoadingTranslateTitle
? "Translating..."
: "Translate to English"}
</button>
)}
</div>
{/* Judul Bahasa Indonesia */}
<div className="mt-2">
<Label className="text-sm font-semibold">
Indonesian Title
</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Masukkan Judul Bahasa Indonesia"
/>
)}
/>
</div>
{/* Judul Bahasa Inggris (muncul setelah klik translate) */}
{translatedTitle && (
<div className="mt-4">
<Label className="text-sm font-semibold">
English Title
</Label>
<Input
size="md"
type="text"
value={translatedTitle}
onChange={(e) => setTranslatedTitle(e.target.value)}
placeholder="English version"
/>
</div>
)}
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
{/* <div className="space-y-2 py-3">
<Label>{t("title", { defaultValue: "Title" })}</Label>
<Controller
control={control}
@ -1581,7 +1667,7 @@ export default function FormImageUpdate() {
{errors.title.message}
</p>
)}
</div>
</div> */}
<div className="flex items-center">
<div className="py-3 w-full space-y-2">
<Label>{t("category", { defaultValue: "Category" })}</Label>
@ -1622,6 +1708,7 @@ export default function FormImageUpdate() {
</div>
</div>
{/* Description section (stacked, auto overwrite English when translated) */}
<div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>
@ -1644,7 +1731,6 @@ export default function FormImageUpdate() {
if (!res.error) {
const resultText =
res?.data?.data?.translations?.[0]?.text || "";
setTranslatedContent(resultText);
}
} catch (err) {
@ -1664,22 +1750,11 @@ export default function FormImageUpdate() {
)}
</div>
{/* Pilihan bahasa untuk posting */}
{roleId === "14" && (
<div className="flex items-center gap-4 mb-2">
<label className="flex items-center gap-2">
<input
type="radio"
value="id"
checked={selectedLang === "id"}
onChange={() => setSelectedLang("id")}
/>
<span>Gunakan Bahasa Indonesia</span>
</label>
</div>
)}
{/* Editor Bahasa Indonesia */}
<div className="mt-3">
<Label className="text-sm font-semibold">
Indonesian Version
</Label>
<Controller
control={control}
name="description"
@ -1690,24 +1765,14 @@ export default function FormImageUpdate() {
/>
)}
/>
{/* Editor Bahasa Inggris */}
{translatedContent && (
<div className="mt-4">
<div className="flex flex-col">
<Label className="text-[15px]">English Version</Label>
<label className="flex items-center gap-2">
<input
type="radio"
value="en"
checked={selectedLang === "en"}
onChange={() => setSelectedLang("en")}
disabled={!translatedContent}
/>
<span>Gunakan Bahasa Inggris</span>
</label>
</div>
{/* Editor Bahasa Inggris (muncul setelah klik translate) */}
{translatedContent && (
<div className="mt-5">
<Label className="text-sm font-semibold">
English Version
</Label>
<CustomEditor
onChange={(val: any) => setTranslatedContent(val)}
initialData={translatedContent}
@ -1721,6 +1786,7 @@ export default function FormImageUpdate() {
</p>
)}
</div>
{/* <div className="py-3 space-y-2">
<Label>
{t("description", { defaultValue: "Description" })}

View File

@ -253,7 +253,7 @@ export default function FormConvertSPIT() {
const loadCategories = async () => {
try {
const response = await listEnableCategory("1");
const response = await listEnableCategory("1", true);
const categories = response?.data?.data?.content || [];
setCategories(categories);
@ -375,6 +375,7 @@ export default function FormConvertSPIT() {
};
const response = await generateDataRewrite(request);
console.log("REWRITE RESPONSE:", response);
if (response?.error) {
throw new Error(response.message);
@ -550,7 +551,6 @@ export default function FormConvertSPIT() {
}
} else {
for (let i = 0; i < files.length; i++) {
placementData.push({
mediaFileId: files[i].contentId,
placements: "polda",

View File

@ -156,8 +156,8 @@ export async function getTagsBySubCategoryId(subCategory: any) {
);
}
export async function listEnableCategory(type: any) {
const url = `media/categories/list?enablePage=0&sort=desc&sortBy=id&type=${type}&isInt=true`;
export async function listEnableCategory(type: any, status?: boolean) {
const url = `media/categories/list?enablePage=0&sort=desc&sortBy=id&type=${type}&isInt=true&isPublish=${status || ""}`;
return httpGetInterceptor(url);
}

View File

@ -63,7 +63,7 @@ export async function getPublicCategoryData(
page: number = 1
) {
return await httpGetInterceptor(
`media/categories/list/publish?enablePage=1&size=12&sort=desc&sortBy=id&page=${
`media/categories/list?enablePage=1&size=12&sort=desc&sortBy=id&page=${
page - 1
}&group=${group}&type=${type}&isInt=${isInt}`
);