feat:save generate article, edit generated article

This commit is contained in:
Rama Priyanto 2024-11-19 18:21:14 +07:00
parent 24418ddb9b
commit c5e4aac78e
8 changed files with 752 additions and 73 deletions

View File

@ -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 (
<Card className="rounded-md border bg-transparent">
<EditGeneratedArticle id={id} />
<Card className="rounded-md border bg-transparent p-6 overflow-auto">
<div className="flex flex-col gap-2">
<Tabs aria-label="Options">
<Tab key="content" title="Content">
<EditGeneratedArticleContent id={id} />
</Tab>
<Tab key="checker" title="Checker">
<EditGeneratedArticleChecker id={id} />
</Tab>
</Tabs>
</div>
</Card>
);
}

View File

@ -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 (
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
<div className="px-2 md:px-4 w-full">
@ -15,12 +30,15 @@ export default function BasicPage() {
New Article
</Button>
</Link>
<Link href="/admin/article/generate">
<Button size="md" color="primary" className="w-min">
<AddIcon />
Generate Article
</Button>
</Link>
<Button
size="md"
color="primary"
className="w-min"
onPress={goGenerate}
>
<AddIcon />
Generate Article
</Button>
</div>
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
<ArticleTable />

View File

@ -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<string[]>([]);
const [warningSEO, setWarningSEO] = useState<string[]>([]);
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
const formOptions = { resolver: zodResolver(formSchema) };
type UserSettingSchema = z.infer<typeof formSchema>;
const {
control,
handleSubmit,
formState: { errors },
setValue,
} = useForm<UserSettingSchema>(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<typeof formSchema>) => {
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 (
<div className="flex flex-col gap-3">
<div className="flex flex-row items-center gap-4">
<CircularProgress
aria-label="Loading..."
value={Number(totalScoreSEO) * 100}
color="warning"
className="w-full"
classNames={{
svg: "w-36 h-36 drop-shadow-md",
value: "text-3xl font-semibold",
}}
showValueLabel={true}
/>
<div className="border border-danger py-2 px-4 rounded-lg">
{errorSEO?.length} Errors
</div>
<div className="border border-warning py-2 px-4 rounded-lg">
{warningSEO?.length} Warnings
</div>
<div className="border border-success py-2 px-4 rounded-lg">
{optimizedSEO?.length} Optimized
</div>
</div>
<Accordion>
<AccordionItem
key="1"
aria-label="Errors"
className="border border-danger px-2 rounded-lg"
title={`${errorSEO?.length} Errors`}
>
<div className="flex flex-col gap-2">
{errorSEO?.map((item: string) => (
<p
key={item}
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
</Accordion>
<Accordion>
<AccordionItem
key="2"
aria-label="Accordion 2"
className="border border-warning px-2 rounded-lg"
title={`${warningSEO?.length} Warnings`}
>
<div className="flex flex-col gap-2">
{warningSEO?.map((item: string) => (
<p
key={item}
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
</Accordion>
<Accordion>
<AccordionItem
key="3"
aria-label="Accordion 3"
className="border border-success px-2 rounded-lg"
title={`${optimizedSEO?.length} Optimized`}
>
<div className="flex flex-col gap-2">
{optimizedSEO?.map((item: string) => (
<p
key={item}
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
</Accordion>
<div className="border-y-2 py-4 my-4">
<Button
className="bg-[#4841A5] hover:bg-[#4f4a97] dark:hover:bg-[#302c61] text-white"
onClick={doRegenerate}
>
Regenerate
</Button>
</div>
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder="Title"
label="Title"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.title && (
<p className="text-red-400 text-sm">{errors.title?.message}</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="flex flex-col">
<Controller
control={control}
name="mainKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="mainKeyword"
placeholder="Keyword"
label="Keyword"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.mainKeyword?.message && (
<p className="text-red-400 text-sm">
{errors.mainKeyword?.message}
</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="additionalKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="additionalKeyword"
placeholder="Additional Keyword"
label="Additional Keyword"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.additionalKeyword?.message && (
<p className="text-red-400 text-sm">
{errors.additionalKeyword?.message}
</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="metaTitle"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="metaTitle"
placeholder="Meta Title"
label="Meta Title"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.metaTitle?.message && (
<p className="text-red-400 text-sm">
{errors.metaTitle?.message}
</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="metaDescription"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="metaDescription"
placeholder="Meta Description"
label="Meta Description"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.metaDescription?.message && (
<p className="text-red-400 text-sm">
{errors.metaDescription?.message}
</p>
)}
</div>
</div>
<div className="flex flex-col">
<Controller
control={control}
name="articleBody"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.articleBody?.message && (
<p className="text-red-400 text-sm">
{errors.articleBody?.message}
</p>
)}
</div>
<Button color="primary" type="submit">
Save
</Button>
</form>
</div>
);
}

View File

@ -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<typeof formSchema>;
const {
control,
handleSubmit,
formState: { errors },
setValue,
} = useForm<UserSettingSchema>(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<typeof formSchema>) => {
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 (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder="Title"
label="Title"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.title && (
<p className="text-red-400 text-sm">{errors.title?.message}</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="flex flex-col">
<Controller
control={control}
name="mainKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="mainKeyword"
placeholder="Keyword"
label="Keyword"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.mainKeyword?.message && (
<p className="text-red-400 text-sm">
{errors.mainKeyword?.message}
</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="additionalKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="additionalKeyword"
placeholder="Additional Keyword"
label="Additional Keyword"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.additionalKeyword?.message && (
<p className="text-red-400 text-sm">
{errors.additionalKeyword?.message}
</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="metaTitle"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="metaTitle"
placeholder="Meta Title"
label="Meta Title"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.metaTitle?.message && (
<p className="text-red-400 text-sm">{errors.metaTitle?.message}</p>
)}
</div>
<div className="flex flex-col">
<Controller
control={control}
name="metaDescription"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="metaDescription"
placeholder="Meta Description"
label="Meta Description"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.metaDescription?.message && (
<p className="text-red-400 text-sm">
{errors.metaDescription?.message}
</p>
)}
</div>
</div>
<div className="flex flex-col">
<Controller
control={control}
name="articleBody"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.articleBody?.message && (
<p className="text-red-400 text-sm">{errors.articleBody?.message}</p>
)}
</div>
<Button color="primary" type="submit">
Save
</Button>
</form>
);
}

View File

@ -1,4 +0,0 @@
export default function EditGeneratedArticle(props: { id: string }) {
const { id } = props;
return <div>{id}</div>;
}

View File

@ -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<string>("");
const [article, setArticle] = React.useState<Selection>(new Set([]));
const [article, setArticle] = useState("");
const [slug, setSlug] = useState<string>("");
const [tags, setTags] = useState<string[]>([]);
const [newTags, setNewTags] = useState<string>("");
@ -107,15 +99,11 @@ export default function GenerateArticleForm() {
const [selectedGeneratedArticleId, setSelectedGeneratedArticleId] =
useState<number>();
const formOptions = { resolver: zodResolver(articleSchema) };
type MicroIssueSchema = z.infer<typeof articleSchema>;
const {
register,
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<MicroIssueSchema>(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 (
<div className="mx-5 my-5 overflow-y-auto">
<div>
@ -253,7 +260,6 @@ export default function GenerateArticleForm() {
<div>
<Input
type="title"
{...register("title")}
value={title}
onChange={(e) => setTitle(e.target.value)}
label="Judul"
@ -262,19 +268,18 @@ export default function GenerateArticleForm() {
labelPlacement="outside"
/>
<div className="text-sm text-red-500">
{title.length === 0 && errors.title && errors.title.message}
{title.length === 0 && <p className="text-danger">Required</p>}
</div>
</div>
<div>
<Select
label="Jenis Artikel"
{...register("article")}
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectedKeys={article}
selectedKeys={[article]}
className="max-w-xs"
onSelectionChange={setArticle}
onChange={(e) => setArticle(e.target.value)}
>
{TypeId.map((data) => (
<SelectItem key={data.key} value={data.key}>
@ -282,10 +287,7 @@ export default function GenerateArticleForm() {
</SelectItem>
))}
</Select>
<div className="text-sm text-red-500">
{errors.article?.message}
</div>
{/* <p>{article}</p> */}
{article === "" && <p className="text-danger">Required</p>}
</div>
<div>
<p className="text-sm mb-1">Category</p>
@ -315,7 +317,6 @@ export default function GenerateArticleForm() {
<div>
<Input
type="text"
{...register("slug")}
value={slug}
onChange={(e) => setSlug(e.target.value)}
label="Slug"
@ -323,14 +324,11 @@ export default function GenerateArticleForm() {
placeholder="Enter Text"
labelPlacement="outside"
/>
<div className="text-sm text-red-500">
{slug.length === 0 && errors.slug && errors.slug.message}
</div>
{slug.length === 0 && <div className=" text-danger">Required</div>}
</div>
<div>
<Input
label="Tags (Optional)"
{...register("tags")}
labelPlacement="outside"
type="text"
value={newTags}
@ -338,9 +336,8 @@ export default function GenerateArticleForm() {
onKeyDown={handleKeyDown}
placeholder="Tambahkan tag baru dan tekan Enter"
/>
<div className="text-sm text-red-500">
{tags.length === 0 && errors.tags && errors.tags.message}
</div>
{tags.length === 0 && <div className=" text-red-500">Required</div>}
<div className="flex gap-2 border border-inherit mt-2 rounded-md p-1 items-center h-11">
{tags.map((tag, index) => (
<Chip
@ -399,17 +396,18 @@ export default function GenerateArticleForm() {
(selectedGeneratedArticleId && content !== "" ? (
<div>
<p className="pb-2">Description</p>
<a
className="text-primary cursor-pointer"
onClick={() => goEdit()}
>
Edit
</a>
<JoditEditor
ref={editor}
value={content}
onChange={(newContent) => setContent(newContent)}
className="dark:text-black"
/>
<div className="text-sm text-red-500">
{content?.length === 0 &&
errors?.description &&
errors?.description?.message}
</div>
</div>
) : selectedGeneratedArticleId && content == "" ? (
<Spinner size="lg" />
@ -449,11 +447,6 @@ export default function GenerateArticleForm() {
onChange={(newContent) => setContent(newContent)}
className="dark:text-black"
/>
<div className="text-sm text-red-500">
{content?.length === 0 &&
errors?.description &&
errors?.description?.message}
</div>
</div>
) : selectedGeneratedArticleId && content == "" ? (
<Spinner size="lg" />
@ -496,11 +489,6 @@ export default function GenerateArticleForm() {
onChange={(newContent) => setContent(newContent)}
className="dark:text-black"
/>
<div className="text-sm text-red-500">
{content?.length === 0 &&
errors?.description &&
errors?.description?.message}
</div>
</div>
) : selectedGeneratedArticleId && content == "" ? (
<Spinner size="lg" />
@ -594,7 +582,13 @@ export default function GenerateArticleForm() {
Cancel
</Button>
</Link>
<Button type="button" color="primary" variant="solid">
<Button
type="button"
color="primary"
variant="solid"
isDisabled={content === ""}
onPress={onSubmit}
>
Save
</Button>
</div>

View File

@ -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);
}

View File

@ -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<targetStore>((set) => ({
setArticleIds: (newTarget: {
singleArticle: number[];
bulkArticle: number[];
rewriteArticle: number[];
}) => {
localStorage.setItem("generated-article", JSON.stringify(newTarget));
set({ articleIds: newTarget });