diff --git a/app/[locale]/(protected)/contributor/content/audio/create/page.tsx b/app/[locale]/(protected)/contributor/content/audio/create/page.tsx new file mode 100644 index 00000000..86df994c --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/audio/create/page.tsx @@ -0,0 +1,15 @@ +import FormAudio from "@/components/form/content/audio-form"; +import SiteBreadcrumb from "@/components/site-breadcrumb"; + +const AudioCreatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default AudioCreatePage; diff --git a/app/[locale]/(protected)/contributor/content/audio/page.tsx b/app/[locale]/(protected)/contributor/content/audio/page.tsx index a0f31679..90b45070 100644 --- a/app/[locale]/(protected)/contributor/content/audio/page.tsx +++ b/app/[locale]/(protected)/contributor/content/audio/page.tsx @@ -1,10 +1,11 @@ "use client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import SiteBreadcrumb from "@/components/site-breadcrumb"; -import { Link, UploadIcon } from "lucide-react"; +import { UploadIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Icon } from "@iconify/react/dist/iconify.js"; import TableAudio from "./components/table-audio"; +import { Link } from "@/components/navigation"; const ReactTableAudioPage = () => { return ( @@ -56,14 +57,16 @@ const ReactTableAudioPage = () => { Konten Audio
- - + + {/* + */}
diff --git a/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx b/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx index 4984ce42..3e3af62b 100644 --- a/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx +++ b/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx @@ -12,6 +12,7 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { format } from "date-fns"; +import { Link } from "@/components/navigation"; const columns: ColumnDef[] = [ { @@ -139,12 +140,18 @@ const columns: ColumnDef[] = [ - + View - + + + + + Edit + + Delete diff --git a/app/[locale]/(protected)/contributor/content/teks/create/page.tsx b/app/[locale]/(protected)/contributor/content/teks/create/page.tsx new file mode 100644 index 00000000..45dcac20 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/teks/create/page.tsx @@ -0,0 +1,16 @@ +import FormAudio from "@/components/form/content/audio-form"; +import FormTeks from "@/components/form/content/teks-form"; +import SiteBreadcrumb from "@/components/site-breadcrumb"; + +const TeksCreatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default TeksCreatePage; diff --git a/app/[locale]/(protected)/contributor/content/teks/detail/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/teks/detail/[id]/page.tsx new file mode 100644 index 00000000..1245c0a1 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/teks/detail/[id]/page.tsx @@ -0,0 +1,16 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormTeksDetail from "@/components/form/content/teks-detail-form"; + +const TeksDetailPage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default TeksDetailPage; diff --git a/app/[locale]/(protected)/contributor/content/teks/page.tsx b/app/[locale]/(protected)/contributor/content/teks/page.tsx index 9ffeb14b..a352d007 100644 --- a/app/[locale]/(protected)/contributor/content/teks/page.tsx +++ b/app/[locale]/(protected)/contributor/content/teks/page.tsx @@ -6,6 +6,7 @@ import { Newspaper, NewspaperIcon, UploadIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Icon } from "@iconify/react/dist/iconify.js"; import TableTeks from "./components/table-teks"; +import { Link } from "@/components/navigation"; const ReactTableTeksPage = () => { return ( @@ -56,15 +57,18 @@ const ReactTableTeksPage = () => {
Konten Teks
+
- - + + {/* + */}
diff --git a/app/[locale]/(protected)/contributor/content/teks/update/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/teks/update/[id]/page.tsx new file mode 100644 index 00000000..c7b8dfec --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/teks/update/[id]/page.tsx @@ -0,0 +1,17 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormImageUpdate from "@/components/form/content/image-update-form"; +import FormTeksUpdate from "@/components/form/content/teks-update-form"; + +const TeksUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default TeksUpdatePage; diff --git a/app/[locale]/(protected)/contributor/content/video/components/columns.tsx b/app/[locale]/(protected)/contributor/content/video/components/columns.tsx index 4984ce42..b58dd079 100644 --- a/app/[locale]/(protected)/contributor/content/video/components/columns.tsx +++ b/app/[locale]/(protected)/contributor/content/video/components/columns.tsx @@ -12,6 +12,7 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { format } from "date-fns"; +import { Link } from "@/components/navigation"; const columns: ColumnDef[] = [ { @@ -139,12 +140,18 @@ const columns: ColumnDef[] = [ - + View - + + + + + Edit + + Delete diff --git a/app/[locale]/(protected)/contributor/content/video/detail/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/video/detail/[id]/page.tsx new file mode 100644 index 00000000..377c0a87 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/video/detail/[id]/page.tsx @@ -0,0 +1,16 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormVideoDetail from "@/components/form/content/video-detail-form"; + +const VideoDetailPage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default VideoDetailPage; diff --git a/app/[locale]/(protected)/contributor/content/video/page.tsx b/app/[locale]/(protected)/contributor/content/video/page.tsx index 264adef7..d3202058 100644 --- a/app/[locale]/(protected)/contributor/content/video/page.tsx +++ b/app/[locale]/(protected)/contributor/content/video/page.tsx @@ -58,16 +58,16 @@ const ReactTableVideoPage = () => { Konten Video
- + - + */}
diff --git a/app/[locale]/(protected)/contributor/content/video/update/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/video/update/[id]/page.tsx new file mode 100644 index 00000000..1b0a481c --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/video/update/[id]/page.tsx @@ -0,0 +1,17 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormImageUpdate from "@/components/form/content/image-update-form"; +import FormVideoUpdate from "@/components/form/content/video-update-form"; + +const VideoUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default VideoUpdatePage; diff --git a/components/form/content/audio-form.tsx b/components/form/content/audio-form.tsx new file mode 100644 index 00000000..02540647 --- /dev/null +++ b/components/form/content/audio-form.tsx @@ -0,0 +1,1021 @@ +"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"; + +const imageSchema = z.object({ + title: z.string().min(1, { message: "Judul diperlukan" }), + description: z + .string() + .min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }), + creatorName: z.string().min(1, { message: "Creator diperlukan" }), + // tags: z.string().min(1, { message: "Judul diperlukan" }), +}); + +interface FileWithPreview extends File { + preview: string; +} + +type Category = { + id: string; + name: string; +}; + +export default function FormAudio() { + const MySwal = withReactContent(Swal); + const router = useRouter(); + const editor = useRef(null); + type ImageSchema = z.infer; + + const [selectedFiles, setSelectedFiles] = useState([]); + const taskId = Cookies.get("taskId"); + const scheduleId = Cookies.get("scheduleId"); + const scheduleType = Cookies.get("scheduleType"); + const roleId = getCookiesDecrypt("urie"); + + const [categories, setCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(); + const [tags, setTags] = useState([]); + const [thumbnail, setThumbnail] = useState(null); + const [preview, setPreview] = useState(null); + const [selectedLanguage, setSelectedLanguage] = useState(""); + + const [selectedSEO, setSelectedSEO] = useState(""); + const [title, setTitle] = useState(""); + const [selectedAdvConfig, setSelectedAdvConfig] = useState(""); + const [editingArticleId, setEditingArticleId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const [articleIds, setArticleIds] = useState([]); + const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); + const [articleBody, setArticleBody] = useState(""); + const [selectedArticleId, setSelectedArticleId] = useState( + null + ); + const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); + const [selectedWritingStyle, setSelectedWritingStyle] = useState(""); + const [selectedSize, setSelectedSize] = useState(""); + const [detailData, setDetailData] = useState(null); + const [articleImages, setArticleImages] = useState([]); + const [isSwitchOn, setIsSwitchOn] = useState(false); + + const [selectedTarget, setSelectedTarget] = useState(""); + const [unitSelection, setUnitSelection] = useState({ + allUnit: false, + mabes: false, + polda: false, + polres: false, + }); + + let fileTypeId = "4"; + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); + + const [files, setFiles] = useState([]); + + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); + + const { + control, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(imageSchema), + }); + + 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 < 5) { + return [...prevIds, newArticleId]; + } else { + const updatedIds = [...prevIds]; + updatedIds[4] = newArticleId; + return updatedIds; + } + }); + + Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds)); + }; + + const handleArticleIdClick = async (id: string) => { + const res = await getDetailArticle(id); + const articleData = res?.data?.data; + + const cleanArticleBody = articleData?.articleBody?.replace( + /]*>/g, + "" + ); + const articleImagesData = articleData?.imagesUrl?.split(","); + + setArticleBody(cleanArticleBody || ""); + setDetailData(articleData); + setSelectedArticleId(id); + setArticleImages(articleImagesData || []); + }; + + const handleAddTag = (e: React.KeyboardEvent) => { + 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 + // setValue("tags", ""); // 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 save = async (data: ImageSchema) => { + loading(); + const finalTags = tags.join(", "); + const finalTitle = isSwitchOn ? title : data.title; + const requestData = { + ...data, + title: finalTitle, + description: data.description, + htmlDescription: data.description, + fileTypeId, + categoryId: selectedCategory, + subCategoryId: selectedCategory, + uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", + statusId: "1", + publishedFor: "6", + creatorName: data.creatorName, + tags: finalTags, + isYoutube: false, + isInternationalMedia: false, + }; + + let id = Cookies.get("idCreate"); + + 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; + + // Upload Thumbnail + const formMedia = new FormData(); + console.log("Thumbnail : ", files[0]); + formMedia.append("file", files[0]); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + } + + // 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, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + + Cookies.remove("idCreate"); + + // MySwal.fire("Sukses", "Data berhasil disimpan.", "success"); + }; + + const onSubmit = (data: ImageSchema) => { + 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 upload = new Upload(file, { + endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, + retryDelays: [0, 3000, 6000, 12_000, 24_000], + chunkSize: 20_000, + metadata: { + mediaid: id, + filename: file.name, + filetype: file.type, + duration, + isWatermark: "true", // hardcode + }, + 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/audio/"); + } + } + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setThumbnail(file); + console.log("Selected Thumbnail:", file); + } + if (file) { + setPreview(URL.createObjectURL(file)); + } + }; + + const renderFilePreview = (file: FileWithPreview) => { + if (file.type.startsWith("image")) { + return ( + {file.name} + ); + } else { + return ; + } + }; + + const handleRemoveFile = (file: FileWithPreview) => { + const uploadedFiles = files; + const filtered = uploadedFiles.filter((i) => i.name !== file.name); + setFiles([...filtered]); + }; + + const fileList = files.map((file) => ( +
+
+
{renderFilePreview(file)}
+
+
{file.name}
+
+ {Math.round(file.size / 100) / 10 > 1000 ? ( + <>{(Math.round(file.size / 100) / 10000).toFixed(1)} + ) : ( + <>{(Math.round(file.size / 100) / 10).toFixed(1)} + )} + {" kb"} +
+
+
+ + +
+ )); + + const handleRemoveAllFiles = () => { + setFiles([]); + }; + + return ( +
+
+ +
+

Form Konten Audio

+
+ {/* Input Title */} +
+ + ( + + )} + /> + {errors.title?.message && ( +

{errors.title.message}

+ )} +
+ +
+
+ + +
+
+
+ +
+ + setIsSwitchOn(checked) + } + /> +
+
+ {isSwitchOn && ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ setSelectedMainKeyword(e.target.value)} + placeholder="Enter Main Keyword" + /> + {/* )} + /> */} +
+
+
+
+ + +
+
+ setTitle(e.target.value)} + placeholder="Generated Title" + /> +
+
+
+
+ + +
+

+ Kata kunci untuk disertakan dalam teks +

+

+ JIka Anda tidak Memberikan kata kunci, kami akan secara + otomatis membuat kata kunci yang relevan dari kata kunci + utama untuk setiap bagian dan menggunakannya untuk membuat + artikel. Untuk menambahkan kata kunci baru, ketik ', + + kata kunci'. +

+
+