diff --git a/app/(admin)/admin/article/generate/edit/[id]/page.tsx b/app/(admin)/admin/article/generate/edit/[id]/page.tsx index 943c141..ad0d41b 100644 --- a/app/(admin)/admin/article/generate/edit/[id]/page.tsx +++ b/app/(admin)/admin/article/generate/edit/[id]/page.tsx @@ -1,14 +1,27 @@ "use client"; -import EditGeneratedArticle from "@/components/form/article/edit-generated-article"; +import EditGeneratedArticleChecker from "@/components/form/article/edit-generated-article-checker-form"; +import EditGeneratedArticleContent from "@/components/form/article/edit-generated-article-content-form"; import { Card } from "@nextui-org/react"; +import { Tab, Tabs } from "@nextui-org/react"; + import { useParams } from "next/navigation"; export default function EditGeneratedArticlePage() { const params = useParams(); const id = String(params.id); + return ( - - + +
+ + + + + + + + +
); } diff --git a/app/(admin)/admin/article/page.tsx b/app/(admin)/admin/article/page.tsx index eb6aa4a..6f9f639 100644 --- a/app/(admin)/admin/article/page.tsx +++ b/app/(admin)/admin/article/page.tsx @@ -1,10 +1,25 @@ "use client"; import { AddIcon } from "@/components/icons"; import ArticleTable from "@/components/table/article-table"; +import generatedArticleIds from "@/store/generated-article-store"; import { Button, Card } from "@nextui-org/react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; export default function BasicPage() { + const router = useRouter(); + const setGeneratedArticleIdStore = generatedArticleIds( + (state) => state.setArticleIds + ); + const goGenerate = () => { + setGeneratedArticleIdStore({ + singleArticle: [], + bulkArticle: [], + rewriteArticle: [], + }); + router.push("/admin/article/generate"); + }; + return (
@@ -15,12 +30,15 @@ export default function BasicPage() { New Article - - - +
diff --git a/components/form/article/edit-generated-article-checker-form.tsx b/components/form/article/edit-generated-article-checker-form.tsx new file mode 100644 index 0000000..c5a63f6 --- /dev/null +++ b/components/form/article/edit-generated-article-checker-form.tsx @@ -0,0 +1,383 @@ +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@nextui-org/input"; +import JoditEditor from "jodit-react"; +import { useEffect, useRef, useState } from "react"; +import { + getDetailArticle, + getSeoScore, + regenerateArticle, + updateManualArticle, +} from "@/service/generate-article"; +import { Button } from "@nextui-org/button"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { useRouter } from "next/navigation"; +import { close, error, loading } from "@/config/swal"; +import { Accordion, AccordionItem, CircularProgress } from "@nextui-org/react"; + +const formSchema = z.object({ + mainKeyword: z.string().min(2, { + message: "Keyword must be at least 2 characters.", + }), + title: z.string().min(2, { + message: "Title must be at least 2 characters.", + }), + additionalKeyword: z.string().min(2, { + message: "Additional Keyword must be at least 2 characters.", + }), + metaTitle: z.string().min(2, { + message: "Meta Title must be at least 2 characters.", + }), + metaDescription: z.string().min(2, { + message: "Meta Description must be at least 2 characters.", + }), + articleBody: z.string().min(2, { + message: "Article Body must be at least 2 characters.", + }), +}); + +export default function EditGeneratedArticleChecker(props: { id: string }) { + const MySwal = withReactContent(Swal); + const router = useRouter(); + const { id } = props; + const editor = useRef(null); + const [totalScoreSEO, setTotalScoreSEO] = useState(); + const [errorSEO, setErrorSEO] = useState([]); + const [warningSEO, setWarningSEO] = useState([]); + const [optimizedSEO, setOptimizedSEO] = useState([]); + + const formOptions = { resolver: zodResolver(formSchema) }; + type UserSettingSchema = z.infer; + const { + control, + handleSubmit, + formState: { errors }, + setValue, + } = useForm(formOptions); + + useEffect(() => { + getArticleDetail(); + fetchSeoScore(); + }, [id]); + + const fetchSeoScore = async () => { + const res = await getSeoScore(id); + if (res.error) { + error(res.message); + return false; + } + setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0); + const errorList: string[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.error, + ]; + setErrorSEO(errorList); + const warningList: string[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.warning, + ]; + setWarningSEO(warningList); + const optimizedList: string[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.optimized, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized, + ]; + setOptimizedSEO(optimizedList); + }; + + const getArticleDetail = async () => { + const res = await getDetailArticle(Number(id)); + // const data = res?.data?.data?.articleBody; + const data = res?.data?.data; + setValue("title", data?.title); + setValue("mainKeyword", data?.mainKeyword); + setValue("additionalKeyword", data?.additionalKeywords); + setValue("metaTitle", data?.metaTitle); + setValue("metaDescription", data?.metaDescription); + setValue("articleBody", data?.articleBody); + }; + + const onSubmit = async (values: z.infer) => { + const request = { + id: Number(id), + title: values.title, + articleBody: values.articleBody, + metaDescription: values.metaDescription, + metaTitle: values.metaTitle, + createdBy: "123123", + }; + console.log("request", request); + loading(); + const res = await updateManualArticle(request); + if (res.error) { + error(res.message); + return false; + } + close(); + successSubmit("/admin/article/generate"); + }; + + function successSubmit(redirect: string) { + MySwal.fire({ + title: "Success", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then((result) => { + if (result.isConfirmed) { + router.push(redirect); + } + }); + } + + const doRegenerate = async () => { + loading(); + const res1 = await regenerateArticle(id); + if (res1.error) { + error(res1.message); + return false; + } + close(); + window.location.reload(); + + getArticleDetail(); + fetchSeoScore(); + }; + + return ( +
+
+ +
+ {errorSEO?.length} Errors +
+
+ {warningSEO?.length} Warnings +
+
+ {optimizedSEO?.length} Optimized +
+
+ + +
+ {errorSEO?.map((item: string) => ( +

+ {item} +

+ ))} +
+
+
+ + +
+ {warningSEO?.map((item: string) => ( +

+ {item} +

+ ))} +
+
+
+ + + +
+ {optimizedSEO?.map((item: string) => ( +

+ {item} +

+ ))} +
+
+
+
+ +
+ +
+ ( + + )} + /> + {errors.title && ( +

{errors.title?.message}

+ )} +
+
+ ( + + )} + /> + {errors.mainKeyword?.message && ( +

+ {errors.mainKeyword?.message} +

+ )} +
+
+ ( + + )} + /> + {errors.additionalKeyword?.message && ( +

+ {errors.additionalKeyword?.message} +

+ )} +
+
+ ( + + )} + /> + {errors.metaTitle?.message && ( +

+ {errors.metaTitle?.message} +

+ )} +
+
+ ( + + )} + /> + {errors.metaDescription?.message && ( +

+ {errors.metaDescription?.message} +

+ )} +
+
+
+ ( + + )} + /> + {errors.articleBody?.message && ( +

+ {errors.articleBody?.message} +

+ )} +
+ + +
+ ); +} diff --git a/components/form/article/edit-generated-article-content-form.tsx b/components/form/article/edit-generated-article-content-form.tsx new file mode 100644 index 0000000..c616e3d --- /dev/null +++ b/components/form/article/edit-generated-article-content-form.tsx @@ -0,0 +1,242 @@ +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@nextui-org/input"; +import JoditEditor from "jodit-react"; +import { useEffect, useRef, useState } from "react"; +import { + getDetailArticle, + updateManualArticle, +} from "@/service/generate-article"; +import { Button } from "@nextui-org/button"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { useRouter } from "next/navigation"; +import { error, loading } from "@/config/swal"; + +const formSchema = z.object({ + mainKeyword: z.string().min(2, { + message: "Keyword must be at least 2 characters.", + }), + title: z.string().min(2, { + message: "Title must be at least 2 characters.", + }), + additionalKeyword: z.string().min(2, { + message: "Additional Keyword must be at least 2 characters.", + }), + metaTitle: z.string().min(2, { + message: "Meta Title must be at least 2 characters.", + }), + metaDescription: z.string().min(2, { + message: "Meta Description must be at least 2 characters.", + }), + articleBody: z.string().min(2, { + message: "Article Body must be at least 2 characters.", + }), +}); + +export default function EditGeneratedArticleContent(props: { id: string }) { + const MySwal = withReactContent(Swal); + const router = useRouter(); + const { id } = props; + const editor = useRef(null); + + const formOptions = { resolver: zodResolver(formSchema) }; + type UserSettingSchema = z.infer; + const { + control, + handleSubmit, + formState: { errors }, + setValue, + } = useForm(formOptions); + + useEffect(() => { + getArticleDetail(); + }, [id]); + + const getArticleDetail = async () => { + const res = await getDetailArticle(Number(id)); + // const data = res?.data?.data?.articleBody; + const data = res?.data?.data; + setValue("title", data?.title); + setValue("mainKeyword", data?.mainKeyword); + setValue("additionalKeyword", data?.additionalKeywords); + setValue("metaTitle", data?.metaTitle); + setValue("metaDescription", data?.metaDescription); + setValue("articleBody", data?.articleBody); + }; + + const onSubmit = async (values: z.infer) => { + const request = { + id: Number(id), + title: values.title, + articleBody: values.articleBody, + metaDescription: values.metaDescription, + metaTitle: values.metaTitle, + createdBy: "123123", + }; + console.log("request", request); + loading(); + const res = await updateManualArticle(request); + if (res.error) { + error(res.message); + return false; + } + close(); + successSubmit("/admin/article/generate"); + }; + + function successSubmit(redirect: string) { + MySwal.fire({ + title: "Success", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then((result) => { + if (result.isConfirmed) { + router.push(redirect); + } + }); + } + + return ( +
+ ( + + )} + /> + {errors.title && ( +

{errors.title?.message}

+ )} +
+
+ ( + + )} + /> + {errors.mainKeyword?.message && ( +

+ {errors.mainKeyword?.message} +

+ )} +
+
+ ( + + )} + /> + {errors.additionalKeyword?.message && ( +

+ {errors.additionalKeyword?.message} +

+ )} +
+
+ ( + + )} + /> + {errors.metaTitle?.message && ( +

{errors.metaTitle?.message}

+ )} +
+
+ ( + + )} + /> + {errors.metaDescription?.message && ( +

+ {errors.metaDescription?.message} +

+ )} +
+
+
+ ( + + )} + /> + {errors.articleBody?.message && ( +

{errors.articleBody?.message}

+ )} +
+ + + ); +} diff --git a/components/form/article/edit-generated-article.tsx b/components/form/article/edit-generated-article.tsx deleted file mode 100644 index 3c95320..0000000 --- a/components/form/article/edit-generated-article.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export default function EditGeneratedArticle(props: { id: string }) { - const { id } = props; - return
{id}
; -} diff --git a/components/form/article/generate-article-form.tsx b/components/form/article/generate-article-form.tsx index e74a741..f1b84ff 100644 --- a/components/form/article/generate-article-form.tsx +++ b/components/form/article/generate-article-form.tsx @@ -32,14 +32,6 @@ import SpeechToTextOperator from "./speech-to-text-form"; import GenerateRewriteArticle from "./generate-rewrite-form"; import SpeechToText from "./speech-to-text-form"; -const articleSchema = z.object({ - title: z.string().min(1, { message: "Required" }), - article: z.string().min(1, { message: "Required" }), - slug: z.string().min(1, { message: "Required" }), - tags: z.string().min(0, { message: "Required" }).optional(), - description: z.string().min(0, { message: "Required" }).optional(), -}); - const dummyCategory = [ { id: 1, @@ -73,7 +65,7 @@ export default function GenerateArticleForm() { const router = useRouter(); const [title, setTitle] = useState(""); - const [article, setArticle] = React.useState(new Set([])); + const [article, setArticle] = useState(""); const [slug, setSlug] = useState(""); const [tags, setTags] = useState([]); const [newTags, setNewTags] = useState(""); @@ -107,15 +99,11 @@ export default function GenerateArticleForm() { const [selectedGeneratedArticleId, setSelectedGeneratedArticleId] = useState(); - const formOptions = { resolver: zodResolver(articleSchema) }; - type MicroIssueSchema = z.infer; - const { - register, - control, - handleSubmit, - setValue, - formState: { errors }, - } = useForm(formOptions); + useEffect(() => { + setCollectSingleArticleId(generatedArticleIdStore.singleArticle); + setCollectBulkArticleId(generatedArticleIdStore.bulkArticle); + setCollectRewriteArticleId(generatedArticleIdStore.rewriteArticle); + }); const TypeId = [ { @@ -160,17 +148,17 @@ export default function GenerateArticleForm() { } }; - async function save(data: any) { + async function save() { const formData = { title: title, typeId: parseInt(String(Array.from(article)[0])), slug: slug, + categoryId: 12, tags: tags.join(","), description: content, htmlDescription: content, }; - console.log("Form Data:", formData); const response = await createArticle(formData); if (response?.error) { @@ -194,7 +182,7 @@ export default function GenerateArticleForm() { }); } - async function onSubmit(data: any) { + async function onSubmit() { MySwal.fire({ title: "Simpan Data", text: "", @@ -205,22 +193,31 @@ export default function GenerateArticleForm() { confirmButtonText: "Simpan", }).then((result) => { if (result.isConfirmed) { - save(data); + save(); } }); } const singleArticleId = (id: number) => { - let temp = [...collectSingleArticleId, id]; + const temp = [...collectSingleArticleId, id]; + const tempStore = generatedArticleIdStore; + tempStore.singleArticle = temp; setCollectSingleArticleId(temp); + setGeneratedArticleIdStore(tempStore); }; const bulkArticleId = (id: number[]) => { - let temp: number[] = [...collectBulkArticleId, ...id]; + const temp: number[] = [...collectBulkArticleId, ...id]; + const tempStore = generatedArticleIdStore; + tempStore.bulkArticle = temp; setCollectBulkArticleId(temp); + setGeneratedArticleIdStore(tempStore); }; const rewriteArticleId = (id: number) => { - let temp = [...collectRewriteArticleId, id]; + const temp = [...collectRewriteArticleId, id]; + const tempStore = generatedArticleIdStore; + tempStore.rewriteArticle = temp; setCollectRewriteArticleId(temp); + setGeneratedArticleIdStore(tempStore); }; useEffect(() => { @@ -242,10 +239,20 @@ export default function GenerateArticleForm() { checkArticleStatus(data); if (data !== null) { setContent(data); + } else { + setContent(""); } } }; + const goEdit = () => { + const temp = generatedArticleIdStore; + temp.singleArticle = collectSingleArticleId; + temp.bulkArticle = collectBulkArticleId; + setGeneratedArticleIdStore(temp); + router.push(`/admin/article/generate/edit/${selectedGeneratedArticleId}`); + }; + return (
@@ -253,7 +260,6 @@ export default function GenerateArticleForm() {
setTitle(e.target.value)} label="Judul" @@ -262,19 +268,18 @@ export default function GenerateArticleForm() { labelPlacement="outside" />
- {title.length === 0 && errors.title && errors.title.message} + {title.length === 0 &&

Required

}
-
- {errors.article?.message} -
- {/*

{article}

*/} + {article === "" &&

Required

}

Category

@@ -315,7 +317,6 @@ export default function GenerateArticleForm() {
setSlug(e.target.value)} label="Slug" @@ -323,14 +324,11 @@ export default function GenerateArticleForm() { placeholder="Enter Text" labelPlacement="outside" /> -
- {slug.length === 0 && errors.slug && errors.slug.message} -
+ {slug.length === 0 &&
Required
}
-
- {tags.length === 0 && errors.tags && errors.tags.message} -
+ {tags.length === 0 &&
Required
} +
{tags.map((tag, index) => (

Description

+ goEdit()} + > + Edit + setContent(newContent)} className="dark:text-black" /> -
- {content?.length === 0 && - errors?.description && - errors?.description?.message} -
) : selectedGeneratedArticleId && content == "" ? ( @@ -449,11 +447,6 @@ export default function GenerateArticleForm() { onChange={(newContent) => setContent(newContent)} className="dark:text-black" /> -
- {content?.length === 0 && - errors?.description && - errors?.description?.message} -
) : selectedGeneratedArticleId && content == "" ? ( @@ -496,11 +489,6 @@ export default function GenerateArticleForm() { onChange={(newContent) => setContent(newContent)} className="dark:text-black" /> -
- {content?.length === 0 && - errors?.description && - errors?.description?.message} -
) : selectedGeneratedArticleId && content == "" ? ( @@ -594,7 +582,13 @@ export default function GenerateArticleForm() { Cancel -
diff --git a/service/generate-article.ts b/service/generate-article.ts index 9d9bbda..41e21c6 100644 --- a/service/generate-article.ts +++ b/service/generate-article.ts @@ -188,3 +188,30 @@ export async function getListArticleDraft(data: ContentBankRequest) { }; return await httpPost("ai-writer/article/datatable", headers, data); } + +export async function updateManualArticle(data: any) { + const headers = { + "content-type": "application/json", + Authorization: + "Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==", + }; + return await httpPost("ai-writer/update-article", headers, data); +} + +export async function getSeoScore(id: string) { + const headers = { + "content-type": "application/json", + Authorization: + "Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==", + }; + return await httpGet(`ai-writer/article/checkSEOScore/${id}`, headers); +} + +export async function regenerateArticle(id: number | string) { + const headers = { + "content-type": "application/json", + Authorization: + "Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==", + }; + return await httpGet(`ai-writer/re-create-article/${id}`, headers); +} diff --git a/store/generated-article-store.tsx b/store/generated-article-store.tsx index c033a84..a73eedb 100644 --- a/store/generated-article-store.tsx +++ b/store/generated-article-store.tsx @@ -1,10 +1,15 @@ import { create } from "zustand"; interface targetStore { - articleIds: { singleArticle: number[]; bulkArticle: number[] }; + articleIds: { + singleArticle: number[]; + bulkArticle: number[]; + rewriteArticle: number[]; + }; setArticleIds: (newTarget: { singleArticle: number[]; bulkArticle: number[]; + rewriteArticle: number[]; }) => void; } @@ -13,7 +18,7 @@ const getInitialTarget = () => { const stored = localStorage.getItem("generated-article"); const initial = stored ? JSON.parse(stored) - : { singleArticle: [], bulkArticle: [] }; + : { singleArticle: [], bulkArticle: [], rewriteArticle: [] }; return initial; } }; @@ -23,6 +28,7 @@ const generatedArticleIds = create((set) => ({ setArticleIds: (newTarget: { singleArticle: number[]; bulkArticle: number[]; + rewriteArticle: number[]; }) => { localStorage.setItem("generated-article", JSON.stringify(newTarget)); set({ articleIds: newTarget });