From eb1bd6903f85aadfcb9d10f9d793f8af8225ae17 Mon Sep 17 00:00:00 2001 From: Rama Priyanto Date: Tue, 4 Feb 2025 15:31:04 +0700 Subject: [PATCH] feat:content rewrite create article, fix landing --- .../form/article/create-article-form.tsx | 72 ++++- components/form/article/edit-article-form.tsx | 4 + .../generate-ai-content-rewrite-form.tsx | 280 ++++++++++++++++++ .../form/article/generate-ai-single-form.tsx | 4 +- components/landing/BodyLayout.tsx | 2 +- components/landing/CategorySatker.tsx | 31 +- components/landing/HeaderNews.tsx | 10 +- components/landing/MedolUpdate.tsx | 78 ++++- components/page/detail-news.tsx | 16 +- components/table/article-table.tsx | 20 +- next.config.js | 20 +- 11 files changed, 482 insertions(+), 55 deletions(-) create mode 100644 components/form/article/generate-ai-content-rewrite-form.tsx diff --git a/components/form/article/create-article-form.tsx b/components/form/article/create-article-form.tsx index 8b40eef..da7bd31 100644 --- a/components/form/article/create-article-form.tsx +++ b/components/form/article/create-article-form.tsx @@ -21,7 +21,13 @@ import { } from "@/service/article"; import ReactSelect from "react-select"; import makeAnimated from "react-select/animated"; -import { Checkbox, Chip } from "@nextui-org/react"; +import { + Checkbox, + Chip, + Select, + SelectItem, + SelectSection, +} from "@nextui-org/react"; import GenerateSingleArticleForm from "./generate-ai-single-form"; import { htmlToString } from "@/utils/global"; import { close, error, loading } from "@/config/swal"; @@ -32,6 +38,7 @@ import { saveManualContext, updateManualArticle, } from "@/service/generate-article"; +import GenerateContentRewriteForm from "./generate-ai-content-rewrite-form"; const CustomEditor = dynamic( () => { @@ -99,6 +106,7 @@ export default function CreateArticleForm() { ); const [thumbnailValidation, setThumbnailValidation] = useState(""); const [diseData, setDiseData] = useState(); + const [selectedWritingType, setSelectedWritingType] = useState("single"); const { getRootProps, getInputProps } = useDropzone({ onDrop: (acceptedFiles) => { @@ -108,6 +116,9 @@ export default function CreateArticleForm() { ]); }, multiple: true, + accept: { + "image/*": [], + }, }); const formOptions = { @@ -475,15 +486,55 @@ export default function CreateArticleForm() { {useAi && ( - { - setDiseData(data); - setValue( - "description", - data?.articleBody ? data?.articleBody : "" - ); - }} - /> +
+ + {selectedWritingType === "single" ? ( + { + setDiseData(data); + setValue( + "description", + data?.articleBody ? data?.articleBody : "" + ); + }} + /> + ) : ( + { + setDiseData(data); + setValue( + "description", + data?.articleBody ? data?.articleBody : "" + ); + }} + /> + )} +
)}

Deskripsi

@@ -575,6 +626,7 @@ export default function CreateArticleForm() { type="file" multiple className="w-fit h-fit" + accept="image/*" onChange={handleFileChange} /> {thumbnailValidation !== "" && ( diff --git a/components/form/article/edit-article-form.tsx b/components/form/article/edit-article-form.tsx index 22fb5b7..6e77004 100644 --- a/components/form/article/edit-article-form.tsx +++ b/components/form/article/edit-article-form.tsx @@ -121,6 +121,9 @@ export default function EditArticleForm(props: { isDetail: boolean }) { ]); }, multiple: true, + accept: { + "image/*": [], + }, }); const formOptions = { @@ -640,6 +643,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) { type="file" multiple className="w-fit h-fit" + accept="image/*" onChange={handleFileChange} /> {thumbnailValidation !== "" && ( diff --git a/components/form/article/generate-ai-content-rewrite-form.tsx b/components/form/article/generate-ai-content-rewrite-form.tsx new file mode 100644 index 0000000..6a6e208 --- /dev/null +++ b/components/form/article/generate-ai-content-rewrite-form.tsx @@ -0,0 +1,280 @@ +"use client"; +import { + Button, + Input, + Select, + SelectItem, + SelectSection, +} from "@nextui-org/react"; +import { FormEvent, useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { close, error, loading } from "@/config/swal"; +import { + generateDataArticle, + getDetailArticle, + getGenerateKeywords, + getGenerateRewriter, + getGenerateTitle, +} from "@/service/generate-article"; +import { delay } from "@/utils/global"; +import dynamic from "next/dynamic"; + +const CustomEditor = dynamic( + () => { + return import("@/components/editor/custom-editor"); + }, + { ssr: false } +); + +const writingStyle = [ + { + id: 1, + name: "Friendly", + }, + { + id: 1, + name: "Professional", + }, + { + id: 3, + name: "Informational", + }, + { + id: 4, + name: "Neutral", + }, + { + id: 5, + name: "Witty", + }, +]; + +const articleSize = [ + { + id: 1, + name: "News (300 - 900 words)", + value: "News", + }, + { + id: 2, + name: "Info (900 - 2000 words)", + value: "Info", + }, + { + id: 3, + name: "Detail (2000 - 5000 words)", + value: "Detail", + }, +]; + +interface DiseData { + id: number; + articleBody: string; + title: string; + metaTitle: string; + description: string; + metaDescription: string; + mainKeyword: string; + additionalKeywords: string; +} + +export default function GenerateContentRewriteForm(props: { + content: (data: DiseData) => void; +}) { + const [selectedWritingSyle, setSelectedWritingStyle] = + useState("Informational"); + const [selectedArticleSize, setSelectedArticleSize] = useState("News"); + const [selectedLanguage, setSelectedLanguage] = useState("id"); + const [mainKeyword, setMainKeyword] = useState(""); + const [articleIds, setArticleIds] = useState([]); + const [selectedId, setSelectedId] = useState(); + const [isLoading, setIsLoading] = useState(true); + + const onSubmit = async () => { + loading(); + const request = { + advConfig: "", + context: mainKeyword, + style: selectedWritingSyle, + sentiment: "Informational", + urlContext: null, + contextType: "article", + lang: selectedLanguage, + createdBy: "123123", + clientId: "humasClientIdtest", + }; + const res = await getGenerateRewriter(request); + close(); + if (res?.error) { + error("Error"); + } + setArticleIds([...articleIds, res?.data?.data?.id]); + }; + + useEffect(() => { + getArticleDetail(); + }, [selectedId]); + + const checkArticleStatus = async (data: string | null) => { + if (data === null) { + delay(7000).then(() => { + getArticleDetail(); + }); + } + }; + + const getArticleDetail = async () => { + if (selectedId) { + const res = await getDetailArticle(selectedId); + const data = res?.data?.data; + checkArticleStatus(data?.articleBody); + if (data?.articleBody !== null) { + setIsLoading(false); + props.content(data); + } else { + setIsLoading(true); + props.content({ + id: data?.id, + articleBody: "", + title: "", + metaTitle: "", + description: "", + metaDescription: "", + additionalKeywords: "", + mainKeyword: "", + }); + } + } + }; + + return ( +
+
+
+ + + +
+
+
+

Text

+
+
+ +
+ {mainKeyword == "" && ( +

Required

+ )} + +
+ {articleIds.length > 0 && ( +
+ {articleIds?.map((id) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/components/form/article/generate-ai-single-form.tsx b/components/form/article/generate-ai-single-form.tsx index c3289b2..f32d23e 100644 --- a/components/form/article/generate-ai-single-form.tsx +++ b/components/form/article/generate-ai-single-form.tsx @@ -388,7 +388,7 @@ export default function GenerateSingleArticleForm(props: { {articleIds.length > 0 && (
- {articleIds?.map((id) => ( + {articleIds?.map((id, index) => ( ))} diff --git a/components/landing/BodyLayout.tsx b/components/landing/BodyLayout.tsx index cfe663b..18ea54c 100644 --- a/components/landing/BodyLayout.tsx +++ b/components/landing/BodyLayout.tsx @@ -13,7 +13,7 @@ export default function BodyLayout() {
- {/* */} +
diff --git a/components/landing/CategorySatker.tsx b/components/landing/CategorySatker.tsx index 423e40f..ddd9a0e 100644 --- a/components/landing/CategorySatker.tsx +++ b/components/landing/CategorySatker.tsx @@ -53,6 +53,12 @@ export default function CategorySatker() { title: "Itwasum", path: "/news/itwasum", }, + { + id: 6, + img: "/assets/satker2/stik-ptik.svg", + title: "STIK-PTIK", + path: "/news/stik-ptik", + }, ]; const SatkerAll = [ @@ -305,14 +311,14 @@ export default function CategorySatker() { return (
-
- {t("kategoriSatker")} +
+

+ {" "} + {t("kategoriSatker")} +

-
- -
-
+
{list.map((item: any, index: any) => ( ))}
-
- -
diff --git a/components/landing/HeaderNews.tsx b/components/landing/HeaderNews.tsx index 5d0b817..dd5779f 100644 --- a/components/landing/HeaderNews.tsx +++ b/components/landing/HeaderNews.tsx @@ -115,12 +115,16 @@ export default function HeaderNews() { className="text-xs text-left m-2 p-2 dark:bg-[#1E1616] bg-white rounded-md flex flex-row gap-2" key={data.id} > - headernews
{ + swiper.navigation.nextEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + swiper.navigation.prevEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + }} > {mediahubUpdate?.map((newsItem: any) => ( @@ -108,7 +130,7 @@ export default function MedolUpdate() { radius="lg" width="300%" alt="tes" - className="object-cover h-[270px]" + className="object-cover !h-[30vh]" src={newsItem.thumbnailLink} /> @@ -141,9 +163,31 @@ export default function MedolUpdate() { navigation={true} modules={[Navigation, Pagination]} spaceBetween={40} - slidesPerView={2} + slidesPerView={1} + breakpoints={{ + // When the window width is less than 640px + 720: { + slidesPerView: 2, // Set slidesPerView to 1 on mobile + }, + }} pagination={true} className="mySwiper" + onSwiper={(swiper) => { + swiper.navigation.nextEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + swiper.navigation.prevEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + }} > {tbnUpdate?.map((newsItem: any) => ( @@ -158,7 +202,7 @@ export default function MedolUpdate() { radius="lg" width="300%" alt="tes" - className="object-cover h-[270px]" + className="object-cover !h-[30vh]" src={newsItem?.image} /> @@ -189,9 +233,31 @@ export default function MedolUpdate() { navigation={true} modules={[Navigation, Pagination]} spaceBetween={40} - slidesPerView={2} + slidesPerView={1} + breakpoints={{ + // When the window width is less than 640px + 720: { + slidesPerView: 2, // Set slidesPerView to 1 on mobile + }, + }} pagination={true} className="mySwiper" + onSwiper={(swiper) => { + swiper.navigation.nextEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + swiper.navigation.prevEl?.classList.add( + "bg-white/70", + "!text-black", + "rounded-full", + "!w-[40px]", + "!h-[40px]" + ); + }} > {inpUpdate?.map((newsItem: any) => ( @@ -206,7 +272,7 @@ export default function MedolUpdate() { radius="lg" width="300%" alt="tes" - className="object-cover h-[270px]" + className="object-cover !h-[30vh]" src={newsItem.image} /> diff --git a/components/page/detail-news.tsx b/components/page/detail-news.tsx index 80e5780..a4ba980 100644 --- a/components/page/detail-news.tsx +++ b/components/page/detail-news.tsx @@ -135,9 +135,11 @@ export default function DetailNews(props: { data: any; listArticle: any }) {

- NextUI hero Image
@@ -149,10 +151,12 @@ export default function DetailNews(props: { data: any; listArticle: any }) { onClick={() => setImageNow(index)} className="cursor-pointer" > - NextUI hero Image ))} diff --git a/components/table/article-table.tsx b/components/table/article-table.tsx index 4082dc9..bc124bc 100644 --- a/components/table/article-table.tsx +++ b/components/table/article-table.tsx @@ -273,16 +273,22 @@ export default function ArticleTable() { label="" variant="bordered" labelPlacement="outside" - placeholder="Select" + placeholder="Kategori" selectionMode="multiple" - selectedKeys={[selectedCategories]} + selectedKeys={selectedCategories} className="w-full" + items={categories} classNames={{ trigger: "border-1" }} - onChange={(e) => { - e.target.value === "" - ? "" - : setSelectedCategories(e.target.value); - console.log("eeess", e.target.value); + onSelectionChange={setSelectedCategories} + renderValue={(items) => { + return items.map((item) => ( + + {item.textValue}, + + )); }} > {categories?.map((category: any) => ( diff --git a/next.config.js b/next.config.js index 153933a..35f0f49 100644 --- a/next.config.js +++ b/next.config.js @@ -1,11 +1,15 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - }, - images: { - domains: ['38.47.180.165'], - }, -} + eslint: { + ignoreDuringBuilds: true, + }, + images: { + remotePatterns: [ + { + hostname: "*", + }, + ], + }, +}; -module.exports = nextConfig +module.exports = nextConfig;