mediahub-fe/components/form/content/video-form.tsx

1164 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, {
ChangeEvent,
useEffect,
useRef,
Fragment,
useState,
} from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Upload } from "tus-js-client";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
uploadThumbnail,
} from "@/service/content/content";
import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/content/ai";
import { getCookiesDecrypt } from "@/lib/utils";
import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react";
import { CloudUpload } from "lucide-react";
import Image from "next/image";
import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu";
import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
interface FileWithPreview extends File {
preview: string;
}
type Category = {
id: string;
name: string;
};
type Option = {
id: string;
label: string;
};
export default function FormVideo() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type VideoSchema = z.infer<typeof videoSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "2";
let progressInfo: any = [];
let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [files, setFiles] = useState<FileWithPreview[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
accept: {
"video/*": [],
},
});
const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
const {
control,
handleSubmit,
getValues,
setValue,
formState: { errors },
} = useForm<VideoSchema>({
resolver: zodResolver(videoSchema),
});
const doGenerateMainKeyword = async () => {
console.log(selectedMainKeyword);
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error during generation process:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateTitle = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
} catch (error) {
console.error("Error generating title:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateKeyword = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error generating keywords:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const handleGenerateArtikel = async () => {
const request = {
advConfig: selectedAdvConfig,
style: selectedWritingStyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
title: title,
imageSource: "Web",
mainKeyword: selectedMainKeyword,
additionalKeywords: selectedSEO,
targetCountry: null,
articleSize: selectedSize,
projectId: 2,
createdBy: roleId,
clientId: "ngDLPPiorplznw2jTqVe3YFCz5xqKfUJ",
};
const res = await generateDataArticle(request);
close();
if (res?.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 3) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[2] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
};
const handleArticleIdClick = async (id: string) => {
setIsLoadingData(true);
let retryCount = 0;
const maxRetries = 20;
try {
const waitForStatusUpdate = async () => {
while (retryCount < maxRetries) {
const res = await getDetailArticle(id);
const articleData = res?.data?.data;
if (articleData?.status === 2) {
return articleData;
}
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
if (inputRef.current) {
inputRef.current.value = ""; // Clear input field
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
useEffect(() => {
async function initState() {
getCategories();
// setVideoActive(fileTypeId == '2');
// getRoles();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category?.data?.data?.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data?.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
const save = async (data: VideoSchema) => {
loading();
const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
}
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; // ✅ Tambahkan properti ini
} = {
...data,
title: finalTitle,
description: finalDescription,
htmlDescription: finalDescription,
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) {
requestData.attachFromScheduleId = Number(scheduleId);
}
if (id == undefined) {
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
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(formMedia, id);
if (responseThumbnail?.error) {
error(responseThumbnail.message);
return false;
}
}
}
// Upload File
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");
});
Cookies.remove("idCreate");
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
};
useEffect(() => {
return () => {
if (preview) {
URL.revokeObjectURL(preview);
}
};
}, [preview]);
const onSubmit = (data: VideoSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
async function uploadResumableFile(
idx: number,
id: string,
file: any,
duration: string
) {
console.log(idx, id, file, duration);
// const placements = getPlacement(file.placements);
// console.log("Placementttt: : ", placements);
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = {
"X-XSRF-TOKEN": csrfToken,
};
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
mediaid: id,
filename: file.name,
filetype: file.type,
duration,
isWatermark: "true", // hardcode
},
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
counterUpdateProgress++;
console.log(counterUpdateProgress);
setProgressList(progressInfo);
setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
uploadPersen = 100;
progressInfo[idx].percentage = 100;
counterUpdateProgress++;
setCounterProgress(counterUpdateProgress);
successTodo();
},
});
upload.start();
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
function successTodo() {
let counter = 0;
for (const element of progressInfo) {
if (element.percentage == 100) {
counter++;
}
}
if (counter == progressInfo.length) {
setIsStartUpload(false);
// hideProgress();
Cookies.remove("idCreate");
successSubmit("/in/contributor/content/video");
}
}
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file); // Simpan file asli tanpa dimodifikasi
setPreview(URL.createObjectURL(file)); // Simpan preview string terpisah
console.log("Selected Thumbnail:", file);
}
};
const renderFilePreview = (file: FileWithPreview) => {
if (file.type.startsWith("image")) {
return (
<Image
width={48}
height={48}
alt={file.name}
src={URL.createObjectURL(file)}
className=" rounded border p-0.5"
/>
);
} else {
return <Icon icon="tabler:file-description" />;
}
};
const handleRemoveFile = (file: FileWithPreview) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
const fileList = files.map((file) => (
<div
key={file.name}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
{/* <div className="file-preview">{renderFilePreview(file)}</div> */}
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewBox="0 0 24 24"
>
<g fill="none" fillRule="evenodd">
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path
fill="currentColor"
d="M20 3a2 2 0 0 1 1.995 1.85L22 5v14a2 2 0 0 1-1.85 1.995L20 21H4a2 2 0 0 1-1.995-1.85L2 19V5a2 2 0 0 1 1.85-1.995L4 3zm0 2H4v14h16zm-9.66 2.638l.518.23l.338.16l.387.19l.43.218l.47.25l.507.28l.266.152l.518.305l.474.292l.43.273l.38.253l.48.33l.364.263l.095.07a1.234 1.234 0 0 1 0 1.98l-.323.235l-.44.308l-.356.239l-.405.263l-.453.283l-.499.3l-.534.309l-.509.282l-.471.25l-.43.22l-.386.188l-.622.288l-.23.1a1.234 1.234 0 0 1-1.714-.99l-.058-.565l-.032-.374l-.042-.664l-.023-.508l-.015-.555l-.004-.294l-.002-.305q0-.31.006-.6l.015-.555l.023-.507l.027-.457l.03-.401l.075-.744a1.235 1.235 0 0 1 1.715-.992m.611 2.501l-.436-.218l-.029.487l-.022.551l-.013.61l-.002.325l.002.325l.013.609l.01.283l.026.52l.015.235l.434-.218l.487-.256l.535-.294l.284-.162l.551-.326l.494-.306l.436-.28l.196-.13l-.407-.27l-.466-.294a30 30 0 0 0-.803-.48l-.283-.161l-.534-.294z"
/>
</g>
</svg>
<div>
<div className=" text-sm text-card-foreground">{file.name}</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className=" border-none rounded-full"
onClick={() => handleRemoveFile(file)}
>
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
));
const handleRemoveAllFiles = () => {
setFiles([]);
};
useEffect(() => {
// Jika input title kosong, isi dengan hasil generate title
if (!getValues("title") && title) {
setValue("title", title);
}
}, [title, getValues, setValue]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<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">{t("form-video")}</p>
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full space-y-2">
<Label>{t("category")}</Label>
<Select
value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => {
console.log("Selected Category ID:", id);
setSelectedCategory(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-row items-center gap-3 py-2">
<Label>{t("ai-assistance")}</Label>
<div className="flex items-center gap-3">
<Switch
defaultChecked={isSwitchOn}
color="primary"
id="c2"
onCheckedChange={(checked: boolean) =>
setIsSwitchOn(checked)
}
/>
</div>
</div>
{isSwitchOn && (
<div>
<div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12">
<Label>{t("language")}</Label>
<Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="id">Indonesia</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>{t("writing-style")}</Label>
<Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="friendly">Friendly</SelectItem>
<SelectItem value="profesional">
Profesional
</SelectItem>
<SelectItem value="informational">
Informational
</SelectItem>
<SelectItem value="neutral">Neutral</SelectItem>
<SelectItem value="witty">Witty</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>{t("article-size")}</Label>
<Select onValueChange={setSelectedSize}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="news">
News (300 - 900 words)
</SelectItem>
<SelectItem value="info">
Info (900 - 2000 words)
</SelectItem>
<SelectItem value="detail">
Detail (2000 - 5000 words)
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>{t("main-keyword")}</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateMainKeyword}
disabled={isLoading}
>
{isLoading ? "Processing..." : "Proses"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={selectedMainKeyword}
onChange={(e) => setSelectedMainKeyword(e.target.value)}
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>{t("title")}</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateTitle}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Generated Title"
/>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>{t("seo")}</Label>
<Button
variant={"outline"}
color="primary"
onClick={doGenerateKeyword}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<p className="font-semibold">
{t("Keywords to include in the text")}
</p>
<p className="text-sm">{t("title-key")}</p>
<div className="mt-3">
<Textarea
value={selectedSEO}
onChange={(e) => setSelectedSEO(e.target.value)}
placeholder="Enter Title"
/>
</div>
</div>
<div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label>
<div className="mt-3">
<Controller
control={control}
name="title"
render={({ field }) => (
<Textarea
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
</div>
</div>
<div>
<div className="my-5">
<Button
// variant={"outline"}
color="primary"
onClick={handleGenerateArtikel}
size="sm"
>
Generate Article
</Button>
</div>
{isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => (
<p
key={index}
className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id
? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500"
}`}
onClick={() => handleArticleIdClick(id)}
>
{"Narasi " + (index + 1)}
</p>
))}
</div>
)}
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<Link
href={`/contributor/content/video/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
)}
</div>
</div>
</div>
</div>
)}
<div className="space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">Loading Proses Data...</p>
</div>
) : (
<CustomEditor
onChange={onChange}
initialData={articleBody || 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("select-file")}</Label>
{/* <Input
id="fileInput"
type="file"
onChange={handleImageChange}
/> */}
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */}
{t("drag-file")}
</h4>
<div className=" text-xs text-muted-foreground">
{t("upload-file-video-max")}
</div>
</div>
</div>
{files.length ? (
<Fragment>
<div>{fileList}</div>
<div className=" flex justify-between gap-2">
{/* <div className="flex flex-row items-center gap-3 py-3">
<Label>{t("watermark")}</Label>
<div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" />
</div>
</div> */}
<Button
color="destructive"
onClick={handleRemoveAllFiles}
>
{t("remove-all")}
</Button>
</div>
</Fragment>
) : null}
</Fragment>
</div>
</div>
{/* Submit Button */}
</div>
</Card>
<div className="w-full lg:w-4/12">
<Card className=" h-[800px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>{t("creator")}</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3">
<Label htmlFor="fileInput">Gambar Utama</Label>
<Input id="fileInput" type="file" onChange={handleImageChange} />
</div>
{preview && (
<div className="mt-3 px-3">
<img
src={preview}
alt="Thumbnail Preview"
className="w-full h-auto rounded"
/>
</div>
)}
<div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">{t("tags")}</Label>
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 ">
{tags.map((tag, index) => (
<span
key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
>
{tag}{" "}
<button
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button"
>
×
</button>
</span>
))}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-3 space-y-2">
<Label>{t("publish-target")}</Label>
{options.map((option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt: any) => opt.id !== "all")
.length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/>
<Label htmlFor={option.id}>{option.label}</Label>
</div>
))}
</div>
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
{t("submit")}
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
{t("cancel")}
</Button>
</div>
</div>
</div>
</div>
</form>
);
}