fix: add button translate in image upload and edit admin, change url api in all filter content
This commit is contained in:
parent
838b492768
commit
92811802cc
|
|
@ -28,21 +28,10 @@ import {
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./column";
|
import columns from "./column";
|
||||||
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { NewCampaignIcon } from "@/components/icon";
|
|
||||||
import { getCategories } from "@/service/settings/settings";
|
import { getCategories } from "@/service/settings/settings";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import CreateCategoryModal from "./create";
|
import CreateCategoryModal from "./create";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -593,6 +593,116 @@ const FilterPage = () => {
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
{t("categories", { defaultValue: "Categories" })}
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
</h3>
|
</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">
|
<ul className="mt-2 space-y-2">
|
||||||
{categories?.map((category: any) => (
|
{categories?.map((category: any) => (
|
||||||
<li key={category?.id}>
|
<li key={category?.id}>
|
||||||
|
|
@ -670,7 +780,7 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div> */}
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
|
|
|
||||||
|
|
@ -609,6 +609,116 @@ const FilterPage = () => {
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
{t("categories", { defaultValue: "Categories" })}
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
</h3>
|
</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">
|
<ul className="mt-2 space-y-2">
|
||||||
{categories.map((category: any) => (
|
{categories.map((category: any) => (
|
||||||
<li key={category?.id}>
|
<li key={category?.id}>
|
||||||
|
|
@ -686,7 +796,7 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div> */}
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
|
|
|
||||||
|
|
@ -627,6 +627,7 @@ const FilterPage = () => {
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
{t("categories", { defaultValue: "Categories" })}
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<ul className="mt-2 space-y-2">
|
<ul className="mt-2 space-y-2">
|
||||||
{categories.map((category: any) => (
|
{categories.map((category: any) => (
|
||||||
<li key={category?.id}>
|
<li key={category?.id}>
|
||||||
|
|
@ -648,65 +649,90 @@ const FilterPage = () => {
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
</ul>
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
|
||||||
}
|
|
||||||
disabled={categoryPage === 1}
|
|
||||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
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>
|
|
||||||
|
|
||||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
{/* ⬇️ 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
|
<button
|
||||||
key={i}
|
key={pageNum}
|
||||||
onClick={() => setCategoryPage(i + 1)}
|
onClick={() => setCategoryPage(pageNum)}
|
||||||
className={`px-3 py-1 border rounded ${
|
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||||
categoryPage === i + 1
|
categoryPage === pageNum
|
||||||
? "bg-[#bb3523] text-white"
|
? "bg-[#bb3523] text-white"
|
||||||
: ""
|
: "bg-white dark:bg-gray-800"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{i + 1}
|
{pageNum}
|
||||||
</button>
|
</button>
|
||||||
))}
|
));
|
||||||
|
})()}
|
||||||
|
|
||||||
<button
|
{/* Tombol Next */}
|
||||||
onClick={() =>
|
<button
|
||||||
setCategoryPage((prev) =>
|
onClick={() =>
|
||||||
Math.min(prev + 1, categoryTotalPages)
|
setCategoryPage((prev) =>
|
||||||
)
|
Math.min(prev + 1, categoryTotalPages)
|
||||||
}
|
)
|
||||||
disabled={categoryPage === categoryTotalPages}
|
}
|
||||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
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"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
fill="currentColor"
|
||||||
width="24"
|
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"
|
||||||
height="24"
|
/>
|
||||||
viewBox="0 0 24 24"
|
</svg>
|
||||||
>
|
</button>
|
||||||
<path
|
</div>
|
||||||
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>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,116 @@ const FilterPage = () => {
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
{t("categories", { defaultValue: "Categories" })}
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
</h3>
|
</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">
|
<ul className="mt-2 space-y-2">
|
||||||
{categories.map((category: any) => (
|
{categories.map((category: any) => (
|
||||||
<li key={category?.id}>
|
<li key={category?.id}>
|
||||||
|
|
@ -687,7 +797,7 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div> */}
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
{/* Garis */}
|
{/* Garis */}
|
||||||
|
|
|
||||||
|
|
@ -958,7 +958,7 @@ export default function FormImageDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the selected target to the category ID from details
|
// 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 filesData = details.files || [];
|
||||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,10 @@ export default function FormImage() {
|
||||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||||
|
|
||||||
|
// 🔹 state untuk translate judul
|
||||||
|
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||||
|
const [isLoadingTranslateTitle, setIsLoadingTranslateTitle] = useState(false);
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
{ id: "all", label: "SEMUA" },
|
{ id: "all", label: "SEMUA" },
|
||||||
{ id: "5", label: "UMUM" },
|
{ id: "5", label: "UMUM" },
|
||||||
|
|
@ -211,6 +215,7 @@ export default function FormImage() {
|
||||||
title: z.string().min(1, { message: t("titleRequired") }),
|
title: z.string().min(1, { message: t("titleRequired") }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(),
|
descriptionOri: z.string().optional(),
|
||||||
|
htmlDescription: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(),
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: t("creatorRequired") }),
|
creatorName: z.string().min(1, { message: t("creatorRequired") }),
|
||||||
files: z
|
files: z
|
||||||
|
|
@ -555,48 +560,53 @@ export default function FormImage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalTags = tags.join(", ");
|
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
|
// pilih description dasar
|
||||||
let finalDescription = isSwitchOn
|
// let finalDescription = isSwitchOn
|
||||||
? data.description
|
// ? data.description
|
||||||
: selectedFileType === "rewrite"
|
// : selectedFileType === "rewrite"
|
||||||
? data.rewriteDescription
|
// ? data.rewriteDescription
|
||||||
: data.descriptionOri;
|
// : 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()) {
|
if (!finalDescription?.trim()) {
|
||||||
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 👉 tempelkan hasil translate ke field form agar ikut terkirim
|
let requestData: any = {
|
||||||
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,
|
|
||||||
title: finalTitle,
|
title: finalTitle,
|
||||||
description: htmlToString(finalDescription), // plain text
|
description: htmlToString(finalDescription), // versi plain text
|
||||||
htmlDescription: finalDescription, // versi HTML
|
htmlDescription: finalDescription, // versi HTML
|
||||||
fileTypeId,
|
fileTypeId,
|
||||||
categoryId: selectedCategory,
|
categoryId: selectedCategory,
|
||||||
|
|
@ -605,11 +615,43 @@ export default function FormImage() {
|
||||||
statusId: "1",
|
statusId: "1",
|
||||||
publishedFor: publishedFor.join(","),
|
publishedFor: publishedFor.join(","),
|
||||||
creatorName: data.creatorName,
|
creatorName: data.creatorName,
|
||||||
tags: finalTags,
|
tags: tags.join(", "),
|
||||||
isYoutube: false,
|
isYoutube: false,
|
||||||
isInternationalMedia: 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");
|
let id = Cookies.get("idCreate");
|
||||||
|
|
||||||
if (scheduleId !== undefined) {
|
if (scheduleId !== undefined) {
|
||||||
|
|
@ -881,8 +923,84 @@ export default function FormImage() {
|
||||||
{t("form-image", { defaultValue: "Form Image" })}
|
{t("form-image", { defaultValue: "Form Image" })}
|
||||||
</p>
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
<div className="py-3 space-y-2">
|
||||||
<div className="space-y-2 py-3">
|
<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>
|
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -902,7 +1020,7 @@ export default function FormImage() {
|
||||||
{errors.title?.message && (
|
{errors.title?.message && (
|
||||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* <div className="flex items-center">
|
{/* <div className="flex items-center">
|
||||||
<div className="py-3 space-y-2 w-full">
|
<div className="py-3 space-y-2 w-full">
|
||||||
|
|
@ -1233,18 +1351,12 @@ export default function FormImage() {
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={(value) => setSelectedFileType(value)}
|
onValueChange={(value) => setSelectedFileType(value)}
|
||||||
value={selectedFileType}
|
value={selectedFileType}
|
||||||
className=" grid-cols-1"
|
className="grid-cols-1"
|
||||||
>
|
>
|
||||||
<div className="">
|
{/* HAPUS radio, ganti jadi preview side-by-side */}
|
||||||
<RadioGroupItem value="original" id="original-file" />
|
|
||||||
<Label htmlFor="original-file">
|
|
||||||
Select Original Description
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Label>
|
<Label className="text-[15px]">
|
||||||
{t("description", { defaultValue: "Description" })}
|
{t("description", { defaultValue: "Description" })}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
|
|
@ -1265,7 +1377,6 @@ export default function FormImage() {
|
||||||
const resultText =
|
const resultText =
|
||||||
res?.data?.data?.translations?.[0]?.text ||
|
res?.data?.data?.translations?.[0]?.text ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
setTranslatedContent(resultText);
|
setTranslatedContent(resultText);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -1284,156 +1395,45 @@ export default function FormImage() {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Editor side-by-side */}
|
||||||
{/* Pilihan bahasa untuk posting */}
|
<div className="flex flex-col gap-3 mt-3">
|
||||||
{roleId === "14" && (
|
<div className="mt-3">
|
||||||
<div className="flex items-center gap-4 mb-2">
|
<Label className="text-sm font-semibold">
|
||||||
<label className="flex items-center gap-2">
|
Indonesian Version
|
||||||
<input
|
</Label>
|
||||||
type="radio"
|
<Controller
|
||||||
value="id"
|
control={control}
|
||||||
checked={selectedLang === "id"}
|
name="descriptionOri"
|
||||||
onChange={() => setSelectedLang("id")}
|
render={({ field: { onChange, value } }) => (
|
||||||
/>
|
<CustomEditor
|
||||||
<span>Gunakan Bahasa Indonesia</span>
|
onChange={onChange}
|
||||||
</label>
|
initialData={value}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Editor Bahasa Indonesia */}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="descriptionOri"
|
|
||||||
render={({ field: { onChange, value } }) => (
|
|
||||||
<CustomEditor
|
|
||||||
onChange={onChange}
|
|
||||||
initialData={value}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
|
|
||||||
<CustomEditor
|
|
||||||
onChange={(val: any) => setTranslatedContent(val)}
|
|
||||||
initialData={translatedContent}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 && (
|
{errors.description?.message && (
|
||||||
<p className="text-red-400 text-sm">
|
<p className="text-red-400 text-sm">
|
||||||
{errors.description.message}
|
{errors.description.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="py-3 space-y-2">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<Label>
|
|
||||||
{t("description", { defaultValue: "Description" })}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
{roleId === "14" && (
|
<p className="text-sm font-semibold mt-3">
|
||||||
<button
|
Content Rewrite
|
||||||
type="button"
|
</p>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</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">
|
<div className="my-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -1444,6 +1444,7 @@ export default function FormImage() {
|
||||||
Content Rewrite
|
Content Rewrite
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showRewriteEditor && (
|
{showRewriteEditor && (
|
||||||
<div>
|
<div>
|
||||||
{isGeneratedArticle && (
|
{isGeneratedArticle && (
|
||||||
|
|
@ -1455,7 +1456,7 @@ export default function FormImage() {
|
||||||
className={`mr-3 px-3 py-2 rounded-md ${
|
className={`mr-3 px-3 py-2 rounded-md ${
|
||||||
selectedArticleId === id
|
selectedArticleId === id
|
||||||
? "bg-green-500 text-white"
|
? "bg-green-500 text-white"
|
||||||
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
|
|
@ -1464,12 +1465,6 @@ export default function FormImage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<div className="py-3 space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{t("file-rewrite", {
|
{t("file-rewrite", {
|
||||||
|
|
@ -1503,6 +1498,7 @@ export default function FormImage() {
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{t("select-file", { defaultValue: "Select File" })}
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,6 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
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 { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
|
|
@ -39,7 +36,6 @@ import {
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { CloudUpload, MailIcon } from "lucide-react";
|
import { CloudUpload, MailIcon } from "lucide-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
@ -174,6 +170,10 @@ export default function FormImageUpdate() {
|
||||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
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 handleThumbnailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
|
|
@ -983,16 +983,22 @@ export default function FormImageUpdate() {
|
||||||
loading();
|
loading();
|
||||||
const finalTags = tags.join(", ");
|
const finalTags = tags.join(", ");
|
||||||
|
|
||||||
// ✅ tentukan isi description sesuai pilihan bahasa
|
// const descFinal =
|
||||||
const descFinal =
|
// selectedLang === "en" && translatedContent
|
||||||
selectedLang === "en" && translatedContent
|
// ? translatedContent
|
||||||
? translatedContent
|
// : data.description;
|
||||||
: data.description;
|
const descFinal = translatedContent || data.description;
|
||||||
|
|
||||||
|
const finalTitle =
|
||||||
|
translatedTitle && translatedTitle.trim() !== ""
|
||||||
|
? translatedTitle
|
||||||
|
: data.title;
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
title: data.title,
|
// title: data.title,
|
||||||
|
title: finalTitle,
|
||||||
description: htmlToString(descFinal), // versi plain text
|
description: htmlToString(descFinal), // versi plain text
|
||||||
htmlDescription: descFinal, // versi HTML
|
htmlDescription: descFinal, // versi HTML
|
||||||
fileTypeId,
|
fileTypeId,
|
||||||
|
|
@ -1560,8 +1566,88 @@ export default function FormImageUpdate() {
|
||||||
{t("form-image", { defaultValue: "Form Image" })}
|
{t("form-image", { defaultValue: "Form Image" })}
|
||||||
</p>
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title dengan translate */}
|
||||||
<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>
|
||||||
|
|
||||||
|
{/* 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>
|
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -1581,7 +1667,7 @@ export default function FormImageUpdate() {
|
||||||
{errors.title.message}
|
{errors.title.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
<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>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
|
|
@ -1622,6 +1708,7 @@ export default function FormImageUpdate() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Description section (stacked, auto overwrite English when translated) */}
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Label>
|
<Label>
|
||||||
|
|
@ -1644,7 +1731,6 @@ export default function FormImageUpdate() {
|
||||||
if (!res.error) {
|
if (!res.error) {
|
||||||
const resultText =
|
const resultText =
|
||||||
res?.data?.data?.translations?.[0]?.text || "";
|
res?.data?.data?.translations?.[0]?.text || "";
|
||||||
|
|
||||||
setTranslatedContent(resultText);
|
setTranslatedContent(resultText);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -1664,50 +1750,29 @@ export default function FormImageUpdate() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</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 Bahasa Indonesia */}
|
||||||
<Controller
|
<div className="mt-3">
|
||||||
control={control}
|
<Label className="text-sm font-semibold">
|
||||||
name="description"
|
Indonesian Version
|
||||||
render={({ field }) => (
|
</Label>
|
||||||
<CustomEditor
|
<Controller
|
||||||
onChange={field.onChange}
|
control={control}
|
||||||
initialData={field.value}
|
name="description"
|
||||||
/>
|
render={({ field }) => (
|
||||||
)}
|
<CustomEditor
|
||||||
/>
|
onChange={field.onChange}
|
||||||
|
initialData={field.value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Editor Bahasa Inggris */}
|
{/* Editor Bahasa Inggris (muncul setelah klik translate) */}
|
||||||
{translatedContent && (
|
{translatedContent && (
|
||||||
<div className="mt-4">
|
<div className="mt-5">
|
||||||
<div className="flex flex-col">
|
<Label className="text-sm font-semibold">
|
||||||
<Label className="text-[15px]">English Version</Label>
|
English Version
|
||||||
<label className="flex items-center gap-2">
|
</Label>
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value="en"
|
|
||||||
checked={selectedLang === "en"}
|
|
||||||
onChange={() => setSelectedLang("en")}
|
|
||||||
disabled={!translatedContent}
|
|
||||||
/>
|
|
||||||
<span>Gunakan Bahasa Inggris</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CustomEditor
|
<CustomEditor
|
||||||
onChange={(val: any) => setTranslatedContent(val)}
|
onChange={(val: any) => setTranslatedContent(val)}
|
||||||
initialData={translatedContent}
|
initialData={translatedContent}
|
||||||
|
|
@ -1721,6 +1786,7 @@ export default function FormImageUpdate() {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div className="py-3 space-y-2">
|
{/* <div className="py-3 space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{t("description", { defaultValue: "Description" })}
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ export default function FormConvertSPIT() {
|
||||||
|
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await listEnableCategory("1");
|
const response = await listEnableCategory("1", true);
|
||||||
const categories = response?.data?.data?.content || [];
|
const categories = response?.data?.data?.content || [];
|
||||||
setCategories(categories);
|
setCategories(categories);
|
||||||
|
|
||||||
|
|
@ -375,6 +375,7 @@ export default function FormConvertSPIT() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await generateDataRewrite(request);
|
const response = await generateDataRewrite(request);
|
||||||
|
console.log("REWRITE RESPONSE:", response);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
|
|
@ -550,7 +551,6 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
|
||||||
placementData.push({
|
placementData.push({
|
||||||
mediaFileId: files[i].contentId,
|
mediaFileId: files[i].contentId,
|
||||||
placements: "polda",
|
placements: "polda",
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,8 @@ export async function getTagsBySubCategoryId(subCategory: any) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listEnableCategory(type: any) {
|
export async function listEnableCategory(type: any, status?: boolean) {
|
||||||
const url = `media/categories/list?enablePage=0&sort=desc&sortBy=id&type=${type}&isInt=true`;
|
const url = `media/categories/list?enablePage=0&sort=desc&sortBy=id&type=${type}&isInt=true&isPublish=${status || ""}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export async function getPublicCategoryData(
|
||||||
page: number = 1
|
page: number = 1
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
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
|
page - 1
|
||||||
}&group=${group}&type=${type}&isInt=${isInt}`
|
}&group=${group}&type=${type}&isInt=${isInt}`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue