feat:generate article form

This commit is contained in:
Rama Priyanto 2024-11-15 17:53:04 +07:00
parent 6931edf52d
commit 922ddcc828
20 changed files with 2839 additions and 129 deletions

View File

@ -0,0 +1,10 @@
import GenerateArticleForm from "@/components/form/article/generate-article-form";
import { Card } from "@nextui-org/react";
export default function GenerateArticle() {
return (
<Card className="rounded-md border bg-transparent">
<GenerateArticleForm />
</Card>
);
}

View File

@ -8,13 +8,19 @@ export default function BasicPage() {
return ( return (
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2"> <div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
<div className="px-2 md:px-4 w-full"> <div className="px-2 md:px-4 w-full">
<div className="rounded-md my-5 px-5 py-2 bg-white dark:bg-[#18181b]"> <div className="rounded-md my-5 px-5 py-2 bg-white dark:bg-[#18181b] flex flex-row gap-3">
<Link href="/admin/article/create"> <Link href="/admin/article/create">
<Button size="md" color="primary" className="w-min"> <Button size="md" color="primary" className="w-min">
<AddIcon /> <AddIcon />
New Article New Article
</Button> </Button>
</Link> </Link>
<Link href="/admin/article/generate">
<Button size="md" color="primary" className="w-min">
<AddIcon />
Generate Article
</Button>
</Link>
</div> </div>
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2"> <div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
<ArticleTable /> <ArticleTable />

View File

@ -0,0 +1,605 @@
"use client";
import { error } from "@/config/swal";
import { createArticle } from "@/service/article";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Button,
Card,
Chip,
Input,
Select,
SelectItem,
SelectSection,
Selection,
Spinner,
} from "@nextui-org/react";
import JoditEditor from "jodit-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import * as z from "zod";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import GenerateSingleArticle from "./generate-single-article-form";
import { getDetailArticle } from "@/service/generate-article";
import { delay } from "@/utils/global";
import GenerateBulkArticle from "./generate-bulk-article-form";
import generatedArticleIds from "@/store/generated-article-store";
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,
label: "Category 1",
value: "category-1",
},
{
id: 2,
label: "Category 2",
value: "category-2",
},
{
id: 3,
label: "Category 3",
value: "category-3",
},
{
id: 4,
label: "Category 4",
value: "category-4",
},
{
id: 5,
label: "Category 5",
value: "category-5",
},
];
export default function GenerateArticleForm() {
const animatedComponents = makeAnimated();
const router = useRouter();
const [title, setTitle] = useState<string>("");
const [article, setArticle] = React.useState<Selection>(new Set([]));
const [slug, setSlug] = useState<string>("");
const [tags, setTags] = useState<string[]>([]);
const [newTags, setNewTags] = useState<string>("");
const editor = useRef(null);
const [content, setContent] = useState("");
const MySwal = withReactContent(Swal);
const [selectedImages, setSelectedImages] = useState<File[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [selectedContentType, setSelectedContentType] =
useState("single-article");
const [collectSingleArticleId, setCollectSingleArticleId] = useState<
number[]
>([]);
const [collectBulkArticleId, setCollectBulkArticleId] = useState<number[]>(
[]
);
const [collectRewriteArticleId, setCollectRewriteArticleId] = useState<
number[]
>([]);
const [generatedTranscriptId, setGeneratedTranscriptId] = useState<number>();
const generatedArticleIdStore = generatedArticleIds(
(state) => state.articleIds
);
const setGeneratedArticleIdStore = generatedArticleIds(
(state) => state.setArticleIds
);
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);
const TypeId = [
{
key: 1,
label: "Article",
},
{
key: 2,
label: "Magazine",
},
];
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
setSelectedImages((prevImages) => [...prevImages, ...files]);
}
};
const handleRemoveImage = (index: number) => {
setSelectedImages((prevImages) => prevImages.filter((_, i) => i !== index));
};
const handleClose = (tagsToRemove: string) => {
setTags(tags.filter((tag) => tag !== tagsToRemove));
if (tags.length === 1) {
setTags([]);
}
};
const handleAddTags = (e: any) => {
if (newTags.trim() !== "") {
setTags([...tags, newTags.trim()]);
setNewTags("");
e.preventDefault();
}
};
const handleKeyDown = (event: any) => {
if (event.key === "Enter") {
handleAddTags(event);
}
};
async function save(data: any) {
const formData = {
title: title,
typeId: parseInt(String(Array.from(article)[0])),
slug: slug,
tags: tags.join(","),
description: content,
htmlDescription: content,
};
console.log("Form Data:", formData);
const response = await createArticle(formData);
if (response?.error) {
error(response.message);
return false;
}
successSubmit("/admin/article");
}
function successSubmit(redirect: any) {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
async function onSubmit(data: any) {
MySwal.fire({
title: "Simpan Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
}
const singleArticleId = (id: number) => {
let temp = [...collectSingleArticleId, id];
setCollectSingleArticleId(temp);
};
const bulkArticleId = (id: number[]) => {
let temp: number[] = [...collectBulkArticleId, ...id];
setCollectBulkArticleId(temp);
};
const rewriteArticleId = (id: number) => {
let temp = [...collectRewriteArticleId, id];
setCollectRewriteArticleId(temp);
};
useEffect(() => {
getArticleDetail();
}, [selectedGeneratedArticleId]);
const checkArticleStatus = async (data: string | null) => {
if (data === null) {
delay(10000).then(() => {
getArticleDetail();
});
}
};
const getArticleDetail = async () => {
if (selectedGeneratedArticleId) {
const res = await getDetailArticle(selectedGeneratedArticleId);
const data = res?.data?.data?.articleBody;
checkArticleStatus(data);
if (data !== null) {
setContent(data);
}
}
};
return (
<div className="mx-5 my-5 overflow-y-auto">
<div>
<Card className="rounded-md p-5 space-y-5">
<div>
<Input
type="title"
{...register("title")}
value={title}
onChange={(e) => setTitle(e.target.value)}
label="Judul"
variant="bordered"
placeholder="Enter Text"
labelPlacement="outside"
/>
<div className="text-sm text-red-500">
{title.length === 0 && errors.title && errors.title.message}
</div>
</div>
<div>
<Select
label="Jenis Artikel"
{...register("article")}
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectedKeys={article}
className="max-w-xs"
onSelectionChange={setArticle}
>
{TypeId.map((data) => (
<SelectItem key={data.key} value={data.key}>
{data.label}
</SelectItem>
))}
</Select>
<div className="text-sm text-red-500">
{errors.article?.message}
</div>
{/* <p>{article}</p> */}
</div>
<div>
<p className="text-sm mb-1">Category</p>
<ReactSelect
className="basic-single text-black z-50"
classNames={{
control: (state: any) =>
"!rounded-xl bg-white !border-1 !border-gray-200",
}}
classNamePrefix="select"
onChange={setSelectedCategory}
closeMenuOnSelect={false}
components={animatedComponents}
isClearable={true}
isSearchable={true}
isMulti={true}
placeholder="Category ..."
name="sub-module"
options={dummyCategory}
/>
<div className="text-sm text-red-500">
{(!selectedCategory || selectedCategory?.length < 1) && (
<p>Required</p>
)}
</div>
</div>
<div>
<Input
type="text"
{...register("slug")}
value={slug}
onChange={(e) => setSlug(e.target.value)}
label="Slug"
variant="bordered"
placeholder="Enter Text"
labelPlacement="outside"
/>
<div className="text-sm text-red-500">
{slug.length === 0 && errors.slug && errors.slug.message}
</div>
</div>
<div>
<Input
label="Tags (Optional)"
{...register("tags")}
labelPlacement="outside"
type="text"
value={newTags}
onChange={(e) => setNewTags(e.target.value)}
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>
<div className="flex gap-2 border border-inherit mt-2 rounded-md p-1 items-center h-11">
{tags.map((tag, index) => (
<Chip
color="primary"
key={index}
onClose={() => handleClose(tag)}
>
{tag}
</Chip>
))}
</div>
</div>
<Select
label="Content Type"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedContentType]}
onChange={(e) =>
e.target.value !== ""
? setSelectedContentType(e.target.value)
: ""
}
className="w-full md:w-1/4 py-4"
>
<SelectSection>
<SelectItem key="single-article">Single Article</SelectItem>
<SelectItem key="bulk-article">Bulk Article</SelectItem>
<SelectItem key="rewrite">Content Rewrite</SelectItem>
<SelectItem key="speech-to-text">Speech to Text</SelectItem>
</SelectSection>
</Select>
{selectedContentType === "single-article" ? (
<div className="flex flex-col">
<GenerateSingleArticle
articleId={(data) => singleArticleId(data)}
/>
<div className="flex flex-wrap gap-3">
{collectSingleArticleId.length > 0 &&
collectSingleArticleId.map((data) => (
<Button
key={data}
color={
selectedGeneratedArticleId === data
? "primary"
: "warning"
}
onPress={() => setSelectedGeneratedArticleId(data)}
>
{data}
</Button>
))}
</div>
{selectedGeneratedArticleId &&
collectSingleArticleId.includes(selectedGeneratedArticleId) &&
(selectedGeneratedArticleId && content !== "" ? (
<div>
<p className="pb-2">Description</p>
<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" />
) : collectSingleArticleId.length > 0 ? (
<p className="text-lg">Select Article ID</p>
) : (
""
))}
</div>
) : selectedContentType === "bulk-article" ? (
<div className="flex flex-col">
<GenerateBulkArticle articleId={(data) => bulkArticleId(data)} />
<div className="flex flex-wrap gap-3 mt-3">
{collectBulkArticleId.length > 0 &&
collectBulkArticleId.map((data) => (
<Button
key={data}
color={
selectedGeneratedArticleId === data
? "primary"
: "warning"
}
onPress={() => setSelectedGeneratedArticleId(data)}
>
{data}
</Button>
))}
</div>
{selectedGeneratedArticleId &&
collectBulkArticleId.includes(selectedGeneratedArticleId) &&
(selectedGeneratedArticleId && content !== "" ? (
<div>
<p className="pb-2">Description</p>
<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" />
) : collectSingleArticleId.length > 0 ? (
<p className="text-lg">Select Article ID</p>
) : (
""
))}
</div>
) : selectedContentType === "rewrite" ? (
<div className="flex flex-col">
<GenerateRewriteArticle
articleId={(data) => rewriteArticleId(data)}
initTranscript={generatedTranscriptId}
/>
<div className="flex flex-wrap gap-3 mt-3">
{collectRewriteArticleId.length > 0 &&
collectRewriteArticleId.map((data) => (
<Button
key={data}
color={
selectedGeneratedArticleId === data
? "primary"
: "warning"
}
onPress={() => setSelectedGeneratedArticleId(data)}
>
{data}
</Button>
))}
</div>
{selectedGeneratedArticleId &&
collectRewriteArticleId.includes(selectedGeneratedArticleId) &&
(selectedGeneratedArticleId && content !== "" ? (
<div>
<p className="pb-2">Description</p>
<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" />
) : collectRewriteArticleId.length > 0 ? (
<p className="text-lg">Select Article ID</p>
) : (
""
))}
</div>
) : selectedContentType === "speech-to-text" ? (
<SpeechToText
generateArticleFromTranscript={(transcriptId) => {
setGeneratedTranscriptId(transcriptId);
setSelectedContentType("rewrite");
}}
/>
) : (
""
)}
<div>
<p>Attachment (Opsional)</p>
<div className="flex items-center justify-center w-full pt-2 ">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-36 border border-gray-100 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-200"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6 ">
<svg
className="w-10 h-10 mb-3 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
></path>
</svg>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
Drag and drop files here
</p>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
{/* or{" "} */}
<span className="font-semibold underline text-amber-800">
Click to upload
</span>
</p>
</div>
<input
id="dropzone-file"
type="file"
onChange={handleImageChange}
className="hidden"
/>
</label>
</div>
{selectedImages?.length > 0 ? (
<div>
<h4>Pratinjau:</h4>
<div className="flex gap-2 pt-2">
{selectedImages.map((image, index) => (
<div key={index} className="flex flex-col items-end">
<Chip
color="danger"
size="sm"
className="cursor-pointer"
onClick={() => handleRemoveImage(index)}
>
X
</Chip>
<img
src={URL.createObjectURL(image)}
alt="Pratinjau Gambar"
style={{ maxWidth: "200px", maxHeight: "200px" }}
/>
</div>
))}
</div>
</div>
) : (
""
)}
</div>
<div className="flex justify-end gap-3">
<Link href={`/admin/article`}>
<Button color="danger" variant="ghost">
Cancel
</Button>
</Link>
<Button type="button" color="primary" variant="solid">
Save
</Button>
</div>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,506 @@
"use client";
import {
Button,
Input,
Select,
SelectItem,
SelectSection,
} from "@nextui-org/react";
import { FormEvent, useState } from "react";
import { Controller, useFieldArray, 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,
getGenerateKeywords,
getGenerateTitle,
getGenerateTopicKeywords,
saveBulkArticle,
} from "@/service/generate-article";
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",
},
];
const formSchema = z.object({
rows: z.array(
z.object({
mainKeyword: z.string().min(1, {
message: "Main Keyword must be at least 2 characters.",
}),
title: z.string().min(1, {
message: "Title must be at least 2 characters.",
}),
additionalKeyword: z.string().min(1, {
message: "Additional Keyword must be at least 2 characters.",
}),
})
),
});
export default function GenerateBulkArticle(props: {
articleId: (data: number[]) => void;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
const [selectedLanguage, setSelectedLanguage] = useState("id");
const formOptions = {
resolver: zodResolver(formSchema),
defaultValues: {
rows: [{ mainKeyword: "", title: "", additionalKeyword: "" }],
},
};
type UserSettingSchema = z.infer<typeof formSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
trigger,
} = useForm<UserSettingSchema>(formOptions);
const { fields, append, remove } = useFieldArray({
control,
name: "rows",
});
const onSubmit = async () => {
loading();
const listData = [];
for (let i = 0; i < fields.length; i++) {
listData.push({
title: getValues(`rows.${i}.title`),
mainKeyword: getValues(`rows.${i}.mainKeyword`),
additionalKeywords: getValues(`rows.${i}.additionalKeyword`),
});
}
const request = {
style: "Friendly",
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "0",
imageSource: "Web",
targetCountry: null,
articleSize: selectedArticleSize,
projectId: 2,
data: listData,
createdBy: "123123",
clientId: "humasClientIdtest",
};
const res = await saveBulkArticle(request);
if (res.error) {
error(res.message);
return false;
}
console.log("res?s", res?.data?.data);
const temp: number[] = [];
res?.data?.data.map((data: any) => temp.push(data.id));
props.articleId(temp);
close();
};
const generateTitle = async (keyword: string | undefined, index: number) => {
if (keyword) {
const req = {
keyword: keyword,
style: selectedWritingSyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
setValue(`rows.${index}.title`, "process...");
const res = await getGenerateTitle(req);
const data = res?.data?.data;
setValue(`rows.${index}.title`, data);
}
};
const generateKeywords = async (
keyword: string | undefined,
index: number
) => {
if (keyword) {
const req = {
keyword: keyword,
style: selectedWritingSyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "0",
clientId: "",
};
setValue(`rows.${index}.additionalKeyword`, "process...");
const res = await getGenerateKeywords(req);
const data = res?.data?.data;
setValue(`rows.${index}.additionalKeyword`, data);
}
};
const processAll = async () => {
let emptyMainKeyword = 0;
const mainKeyword = getValues(`rows.0.mainKeyword`);
for (let i = 0; i < fields.length; i++) {
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
if (mainKeyNow === "") {
emptyMainKeyword++;
}
}
if (mainKeyword !== "") {
if (emptyMainKeyword > 0) {
loading();
const res = await getGenerateTopicKeywords({
keyword: mainKeyword,
count: emptyMainKeyword,
});
const data = res?.data?.data;
let j = 0;
for (let i = 0; i < fields.length; i++) {
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
if (mainKeyNow === "") {
setValue(`rows.${i}.mainKeyword`, data[j]);
j++;
}
}
for (let i = 0; i < fields.length; i++) {
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
if (getValues(`rows.${i}.title`) == "") {
generateTitle(mainKeyNow, i);
}
if (getValues(`rows.${i}.additionalKeyword`) == "") {
generateKeywords(mainKeyNow, i);
}
}
close();
} else {
loading();
for (let i = 0; i < fields.length; i++) {
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
if (getValues(`rows.${i}.title`) == "") {
generateTitle(mainKeyNow, i);
}
if (getValues(`rows.${i}.additionalKeyword`) == "") {
generateKeywords(mainKeyNow, i);
}
}
close();
}
}
};
const processRow = (index: number) => {
const addKeyword = getValues(`rows.${index}.mainKeyword`);
console.log("index", index, addKeyword);
generateTitle(addKeyword, index);
generateKeywords(addKeyword, index);
};
const validateFields = async () => {
const validation = await trigger();
if (validation) {
onSubmit();
}
};
return (
<fieldset>
<form
className="flex flex-col gap-2 w-full"
onSubmit={handleSubmit(onSubmit)}
>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-full">
<Select
label="Writing Style"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedWritingSyle]}
onChange={(e) =>
e.target.value !== ""
? setSelectedWritingStyle(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{writingStyle.map((style) => (
<SelectItem key={style.name}>{style.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Article Size"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedArticleSize]}
onChange={(e) =>
e.target.value !== ""
? setSelectedArticleSize(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{articleSize.map((size) => (
<SelectItem key={size.value}>{size.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Language"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedLanguage]}
onChange={(e) =>
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
}
className="w-full"
>
<SelectSection>
<SelectItem key="id">Indonesia</SelectItem>
<SelectItem key="en">English</SelectItem>
</SelectSection>
</Select>
</div>
<div className={`flex flex-col md:gap-3 `}>
{fields.map((field, index) => (
<div
key={field.id}
className={` grid grid-cols-1 md:grid-cols-3 gap-5 w-full ${
fields.length === 1
? ""
: "border-b-2 md:border-none py-3 md:py-1"
}`}
>
<div>
<div className="text-[10px] md:text-base flex items-center justify-between">
<div className="flex flex-row items-center gap-1">
<p className="text-sm">Main Keyword</p>
<Button
color="primary"
size="sm"
onClick={() => {
if (index === 0) {
processAll();
} else {
processRow(index);
}
}}
>
Process {index === 0 ? "All" : ""}
</Button>
</div>
<div className="w-[42px] flex flex-row gap-1 md:hidden">
{index === fields.length - 1 && (
<button
className="w-[20px] mb-2 "
type="button"
onClick={() =>
append({
mainKeyword: "",
title: "",
additionalKeyword: "",
})
}
>
+
</button>
)}
{index !== 0 && (
<a
onClick={() => remove(index)}
className="cursor-pointer mb-2 w-[20px] h-[20px]text-center rounded-full flex justify-center items-center"
>
-
</a>
)}
</div>
</div>
<Controller
control={control}
name={`rows.${index}.mainKeyword`}
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="mainKeyword"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
isDisabled={value === "process..."}
variant="bordered"
/>
)}
/>
</div>
<div>
<div className="text-[10px] md:text-base flex items-center gap-1">
<p className="text-sm">Title</p>
<Button
color="primary"
size="sm"
onClick={() =>
generateTitle(
getValues(`rows.${index}.mainKeyword`),
index
)
}
>
Process
</Button>
</div>
<Controller
control={control}
name={`rows.${index}.title`}
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
isDisabled={value === "process..."}
variant="bordered"
/>
)}
/>
</div>
<div className="w-full flex flex-row items-end gap-1">
<div className="full grow">
<div>
<div className="text-[10px] md:text-base flex items-center gap-1">
<p className="text-sm">SEO</p>
<Button
color="primary"
size="sm"
onClick={() =>
generateKeywords(
getValues(`rows.${index}.mainKeyword`),
index
)
}
>
Process
</Button>
</div>
<Controller
control={control}
name={`rows.${index}.additionalKeyword`}
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
isDisabled={value === "process..."}
variant="bordered"
/>
)}
/>
</div>
</div>
<div className="w-[42px] flex-row gap-1 hidden md:flex">
{index === fields.length - 1 && (
<button
className="w-[20px] mb-2 "
type="button"
onClick={() =>
append({
mainKeyword: "",
title: "",
additionalKeyword: "",
})
}
>
+
</button>
)}
{index !== 0 && (
<a
onClick={() => remove(index)}
className="cursor-pointer mb-2 w-[20px] h-[20px]text-center rounded-full flex justify-center items-center"
>
-
</a>
)}
</div>
</div>
</div>
))}
</div>
<Button
type="button"
onClick={() => validateFields()}
// disabled={validateFields()}
color="primary"
>
Generate
</Button>
</form>
</fieldset>
);
}

View File

@ -0,0 +1,273 @@
"use client";
import {
Button,
Input,
Select,
SelectItem,
SelectSection,
Textarea,
} 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 { getGenerateRewriter } from "@/service/generate-article";
import TranscriptDraftTable from "@/components/table/disestages/transcript-draft-table";
import ArticleDraftTable from "@/components/table/disestages/article-draft-table";
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",
},
];
const formSchema = z.object({
field1: z.string().min(2, {
message: "Required",
}),
advancedConfiguration: z.string().optional(),
});
export default function GenerateRewriteArticle(props: {
articleId: (data: number) => void;
initTranscript?: number;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
const [selectedLanguage, setSelectedLanguage] = useState("id");
const [contextType, setContextType] = useState("text");
useEffect(() => {
if (props.initTranscript) {
setContextType("transcript");
}
}, [props.initTranscript]);
const formOptions = { resolver: zodResolver(formSchema) };
type UserSettingSchema = z.infer<typeof formSchema>;
const {
control,
handleSubmit,
formState: { errors },
setValue,
} = useForm<UserSettingSchema>(formOptions);
const onSubmit = async (values: z.infer<typeof formSchema>) => {
loading();
const request = {
advConfig: values.advancedConfiguration || "",
context: contextType == "url" ? null : values.field1,
style: selectedWritingSyle,
sentiment: "Humorous",
clientId: "humasClientIdtest",
createdBy: "123123",
contextType: contextType,
urlContext: contextType === "url" ? values.field1 : null,
lang: selectedLanguage,
};
const res = await getGenerateRewriter(request);
close();
if (res?.error) {
error("Error");
}
props.articleId(res?.data?.data?.id);
};
useEffect(() => {
setValue("field1", "");
}, [contextType]);
return (
<fieldset>
<form
className="flex flex-col gap-2 w-full"
onSubmit={handleSubmit(onSubmit)}
>
<div className="grid grid-cols-1 md:grid-cols-4 gap-5 w-full">
<Select
label="Context Type"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[contextType]}
onChange={(e) =>
e.target.value !== "" ? setContextType(e.target.value) : ""
}
className="w-full"
>
<SelectSection>
<SelectItem key="text">Text</SelectItem>
<SelectItem key="article">Article</SelectItem>
<SelectItem key="transcript">Transcript</SelectItem>
<SelectItem key="url">URL</SelectItem>
</SelectSection>
</Select>
<Select
label="Writing Style"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedWritingSyle]}
onChange={(e) =>
e.target.value !== ""
? setSelectedWritingStyle(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{writingStyle.map((style) => (
<SelectItem key={style.name}>{style.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Article Size"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedArticleSize]}
onChange={(e) =>
e.target.value !== ""
? setSelectedArticleSize(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{articleSize.map((size) => (
<SelectItem key={size.value}>{size.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Language"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedLanguage]}
onChange={(e) =>
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
}
className="w-full"
>
<SelectSection>
<SelectItem key="id">Indonesia</SelectItem>
<SelectItem key="en">English</SelectItem>
</SelectSection>
</Select>
</div>
<div className="flex flex-col gap-3 mt-3">
{(contextType === "text" || contextType === "url") && (
<p className="text-sm">
{contextType === "text" ? "Enter your text here" : "Insert URL"}
</p>
)}
{(contextType === "text" || contextType === "url") && (
<Controller
control={control}
name="field1"
render={({ field: { onChange, value } }) =>
contextType === "text" ? (
<Textarea
label=""
id="adv_config"
variant="bordered"
disableAnimation
className="pb-3"
placeholder="Type your custom instruction here!"
value={value}
onChange={onChange}
/>
) : (
<Input
type="text"
id="mainKeyword"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)
}
/>
)}
{errors.field1?.message && (
<p className="text-red-400 text-sm">{errors.field1?.message}</p>
)}
{contextType === "article" && (
<>
<ArticleDraftTable
articleContent={(data) => setValue("field1", data)}
/>
{errors.field1?.message && (
<p className="text-red-400 text-sm">Select Transcript</p>
)}
</>
)}
{contextType === "transcript" && (
<>
<TranscriptDraftTable
transriptContent={(data) => setValue("field1", data)}
initTranscriptId={String(props.initTranscript)}
/>
{errors.field1?.message && (
<p className="text-red-400 text-sm">Select Transcript</p>
)}
</>
)}
<Button
color="primary"
className="my-5 w-full py-5 text-xs md:text-base"
type="submit"
>
Generate
</Button>
</div>
</form>
</fieldset>
);
}

View File

@ -0,0 +1,334 @@
"use client";
import {
Button,
Input,
Select,
SelectItem,
SelectSection,
} from "@nextui-org/react";
import { FormEvent, 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,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/generate-article";
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",
},
];
const singleArticleSchema = z.object({
mainKeyword: z.string().min(2, {
message: "Main Keyword must be at least 2 characters.",
}),
title: z.string().min(2, {
message: "Main Keyword must be at least 2 characters.",
}),
additionalKeyword: z.string().min(2, {
message: "Main Keyword must be at least 2 characters.",
}),
});
export default function GenerateSingleArticle(props: {
articleId: (data: number) => void;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
const [selectedLanguage, setSelectedLanguage] = useState("id");
const formOptions = { resolver: zodResolver(singleArticleSchema) };
type UserSettingSchema = z.infer<typeof singleArticleSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
} = useForm<UserSettingSchema>(formOptions);
const generateAll = async (keyword: string | undefined) => {
if (keyword) {
generateTitle(keyword);
generateKeywords(keyword);
}
};
const generateTitle = async (keyword: string | undefined) => {
if (keyword) {
loading();
const req = {
keyword: keyword,
style: selectedWritingSyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
const res = await getGenerateTitle(req);
const data = res?.data?.data;
setValue("title", data);
close();
}
};
const generateKeywords = async (keyword: string | undefined) => {
if (keyword) {
const req = {
keyword: keyword,
style: selectedWritingSyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "0",
clientId: "",
};
loading();
const res = await getGenerateKeywords(req);
const data = res?.data?.data;
setValue("additionalKeyword", data);
close();
}
};
const onSubmit = async (values: z.infer<typeof singleArticleSchema>) => {
loading();
const request = {
advConfig: "",
style: selectedWritingSyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
title: values.title,
imageSource: "Web",
mainKeyword: values.mainKeyword,
additionalKeywords: values.additionalKeyword,
targetCountry: null,
articleSize: selectedArticleSize,
projectId: 2,
createdBy: "123123",
clientId: "humasClientIdtest",
};
console.log("reqq", request);
const res = await generateDataArticle(request);
close();
console.log("res", res?.data?.data);
if (res?.error) {
error("Error");
}
props.articleId(res?.data?.data?.id);
};
return (
<fieldset>
<form
className="flex flex-col gap-2 w-full"
onSubmit={handleSubmit(onSubmit)}
>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-full">
<Select
label="Writing Style"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedWritingSyle]}
onChange={(e) =>
e.target.value !== ""
? setSelectedWritingStyle(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{writingStyle.map((style) => (
<SelectItem key={style.name}>{style.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Article Size"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedArticleSize]}
onChange={(e) =>
e.target.value !== ""
? setSelectedArticleSize(e.target.value)
: ""
}
className="w-full"
>
<SelectSection>
{articleSize.map((size) => (
<SelectItem key={size.value}>{size.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Language"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedLanguage]}
onChange={(e) =>
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
}
className="w-full"
>
<SelectSection>
<SelectItem key="id">Indonesia</SelectItem>
<SelectItem key="en">English</SelectItem>
</SelectSection>
</Select>
</div>
<div className="flex flex-col gap-3 mt-3">
<div className="flex flex-row gap-2 items-center">
<p className="text-sm">Main Keyword</p>
<Button
color="primary"
size="sm"
onPress={() => generateAll(getValues("mainKeyword"))}
>
Process
</Button>
</div>
<Controller
control={control}
name="mainKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="mainKeyword"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.mainKeyword && (
<p className="text-red-400 text-sm">
{errors.mainKeyword?.message}
</p>
)}
<div className="flex flex-row gap-2 items-center">
<p className="text-sm">Title</p>
<Button
color="primary"
size="sm"
onPress={() => generateAll(getValues("mainKeyword"))}
>
Generate
</Button>
</div>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
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="flex flex-row gap-2 items-center">
<p className="text-sm">Additional Keyword</p>
<Button
color="primary"
size="sm"
onPress={() => generateAll(getValues("mainKeyword"))}
>
Generate
</Button>
</div>
<Controller
control={control}
name="additionalKeyword"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="additionalKeyword"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
variant="bordered"
/>
)}
/>
{errors.additionalKeyword && (
<p className="text-red-400 text-sm">
{errors.additionalKeyword?.message}
</p>
)}
<Button
color="primary"
className="my-5 w-full py-5 text-xs md:text-base"
type="submit"
>
Generate
</Button>
</div>
</form>
</fieldset>
);
}

View File

@ -0,0 +1,288 @@
"use client";
import { AddIcon, TimesIcon } from "@/components/icons";
import { Input, Textarea } from "@nextui-org/input";
import {
Button,
Checkbox,
CircularProgress,
Divider,
Select,
SelectItem,
SelectSection,
} from "@nextui-org/react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { Suspense, useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useForm } from "react-hook-form";
import Cookies from "js-cookie";
import { close, error, loading } from "@/config/swal";
import {
generateSpeechToText,
getTranscriptById,
} from "@/service/generate-article";
import { delay } from "@/utils/global";
// import OperatorArticleTable from "@/components/table/assistant/create-content/operator/operator-article-table";
// import OperatorTranscriptTable from "@/components/table/assistant/create-content/operator/operator-transcript-table";
// import OperatorArticleDraftTable from "@/components/table/assistant/create-content/operator/operator-draft-article-table";
// import OperatorDetailDraftArticle from "@/components/main/multipool-assistant/operator-detail-draft-article";
const createContentOnlineMediaSchema = z.object({
title: z.string().min(1, { message: "Required" }),
prompt: z.string().min(1, { message: "Required" }),
});
export default function SpeechToText(props: {
generateArticleFromTranscript: (transcriptId: number) => void;
}) {
const searchParam = useSearchParams();
const transcriptQuery = searchParam.get("transcriptId");
const roleId = Cookies.get("urie");
let [files, setFiles] = useState<File[]>([]);
const [handleFileStatus, setHandleFileStatus] = useState("");
const [detailTranscript, setDetailTranscript] = useState<string>("");
const [generatedTranscriptId, setGeneratedTranscriptId] = useState<number>();
const [advanceConfig, setAdvanceConfig] = useState(false);
const [language, setLanguage] = useState("id");
const formOptions = { resolver: zodResolver(createContentOnlineMediaSchema) };
type CreateContentSchema = z.infer<typeof createContentOnlineMediaSchema>;
const {
register,
setValue,
handleSubmit,
formState: { errors },
} = useForm<CreateContentSchema>(formOptions);
const handleFileChange = (event: any) => {
if (files.length < 1) {
const newFiles: FileList | null = event.target.files;
if (newFiles) {
const allowedExtensions = [".mp3"];
let temp: File[] = [...files]; // Salin file-file yang sudah ada
for (let i = 0; i < newFiles.length; i++) {
const file = newFiles[i];
const fileExtension = file.name.split(".").pop()?.toLowerCase();
if (
fileExtension &&
allowedExtensions.includes(`.${fileExtension}`)
) {
temp.push(file);
} else {
alert("The supported File formats are .mp3");
}
}
setFiles(temp);
}
}
};
const removeFile = (name: string) => {
const arrayFile: File[] = [];
for (const element of files) {
if (element.name !== name) {
arrayFile.push(element);
}
}
setFiles(arrayFile);
};
const handleGenerateTranscript = (data: any) => {
if (files.length > 0) {
doGenerate(data);
setHandleFileStatus("");
} else {
setHandleFileStatus("Required");
}
};
const doGenerate = async (data: any) => {
loading();
let formData = new FormData();
console.log("data.", data.prompt);
formData.append("filename", files[0]);
formData.append("title", data.title);
formData.append("prompt", data.prompt);
formData.append("language", language);
formData.append("clientId", "humasClientIdTest");
formData.append("createdBy", "123");
const res = await generateSpeechToText(formData);
setGeneratedTranscriptId(res?.data?.data?.id);
close();
initFetch(res?.data?.data?.id);
return false;
};
const checkTranscriptStatus = async (id: number) => {
delay(7000).then(() => {
initFetch(id);
});
};
const initFetch = async (id?: number) => {
if (id) {
const res = await getTranscriptById(id);
console.log("stat", res?.data?.data);
if (res?.data?.data?.status === 0) {
setDetailTranscript("");
checkTranscriptStatus(id);
} else {
setDetailTranscript(res?.data?.data.content);
}
}
};
return (
<Suspense>
<div className="w-full">
<div className="w-full text-sm">
<form method="POST" onSubmit={handleSubmit(handleGenerateTranscript)}>
<div className="flex flex-col">
<p className="font-bold">Upload an Audio File</p>
<p>The supported File formats are .mp3</p>
<p>Max: 25mb</p>
{files.length < 1 && (
<label htmlFor="dropzone-file">
<div className="mt-3 w-[150px] flex justify-center items-center bg-success p-2 text-white rounded-lg gap-2 cursor-pointer">
<input
id="dropzone-file"
type="file"
multiple
accept=".mp3"
className="hidden"
onChange={handleFileChange}
/>
Add file <AddIcon />
</div>
</label>
)}
{handleFileStatus !== "" && files.length < 1 && (
<p className="text-tiny text-danger">{handleFileStatus}</p>
)}
{files.length > 0 &&
files.map((list) => (
<div
key={list.name}
className="w-[350px] mt-3 flex flex-row items-center justify-between border-1 rounded-lg p-2"
>
{list.name}
<a
className="cursor-pointer"
onClick={() => removeFile(list.name)}
>
<TimesIcon />
</a>
</div>
))}
<div className="w-full md:w-1/2 pt-2 pb-4">
<p className="text-black text-sm mb-1">Language</p>
<Select
label=""
labelPlacement="outside"
selectedKeys={[language]}
className="w-full"
variant="bordered"
onChange={(e) =>
e.target.value == "" ? "" : setLanguage(e.target.value)
}
renderValue={(items) => {
return items.map((item) => (
<span
key={item.props?.value}
className="text-black text-xs"
>
{item.textValue}
</span>
));
}}
>
<SelectSection>
<SelectItem key="id">Indonesia</SelectItem>
<SelectItem key="en">English</SelectItem>
</SelectSection>
</Select>
</div>
<div className="w-full md:w-1/2 mt-3">
<p className="text-black text-sm mb-1">
Title<span className="text-red-500">*</span>
</p>
<Input
type="text"
id="keyword"
label=""
className="mb-3"
placeholder="Enter title"
labelPlacement="outside"
variant="bordered"
{...register("title")}
/>
{errors.title?.message && (
<p className="text-tiny text-danger">
{errors.title?.message}
</p>
)}
</div>
<div className="w-full md:w-1/2 mt-3">
<p className="text-black text-sm mb-1">
Keyword<span className="text-red-500">*</span>
</p>
<Textarea
label=""
id="seo"
variant="bordered"
disableAnimation
disableAutosize
minRows={10}
placeholder="Write your text"
{...register("prompt")}
/>
{errors.prompt?.message && (
<p className="text-tiny text-danger">
{errors.prompt?.message}
</p>
)}
</div>
</div>
<div className="flex justify-between mt-3">
<Button className="w-[200px]" color="primary" type="submit">
Generate
</Button>
</div>
{generatedTranscriptId && (
<div className="flex flex-col gap-3 my-3">
<p className="text-xl font-semibold">Result</p>
{detailTranscript === "" ? (
<CircularProgress />
) : (
<p>{detailTranscript}</p>
)}
</div>
)}
{detailTranscript !== "" && (
<Button
className="w-[200px]"
color="primary"
onPress={() => {
generatedTranscriptId &&
props.generateArticleFromTranscript(generatedTranscriptId);
}}
>
Generate Article
</Button>
)}
</form>
</div>
</div>
</Suspense>
);
}

View File

@ -346,6 +346,7 @@ export default function FormArticle() {
<input <input
id="dropzone-file" id="dropzone-file"
type="file" type="file"
className="hidden"
onChange={handleImageChange} onChange={handleImageChange}
/> />
</label> </label>

View File

@ -27,9 +27,9 @@ export const AdminLayout = ({ children }: Props) => {
return ( return (
<SidebarProvider> <SidebarProvider>
<div className="flex items-center justify-between h-screen p-1 md:p-4"> <div className="h-screen flex items-center flex-row p-1 md:p-4">
<Sidebar sidebarData={isOpen} updateSidebarData={updateSidebarData} /> <Sidebar sidebarData={isOpen} updateSidebarData={updateSidebarData} />
<div className={`h-full w-full flex flex-col gap-4`}> <div className={`w-full h-full flex flex-col gap-4`}>
<Breadcrumb /> <Breadcrumb />
{children} {children}
</div> </div>

View File

@ -221,114 +221,66 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
return ( return (
<div <div
className={`hidden md:flex h-full ${ className={`hidden md:flex h-full flex-grow ${
isOpen ? "min-w-[290px]" : "min-w-[80px]" isOpen ? "min-w-[290px]" : "min-w-[80px]"
}`} }`}
> >
<div <div
className={`will-change relative flex h-full flex-col rounded-lg p-4 mb-0 bg-gray-100 dark:bg-stone-950 z-40 transition-width !ease-in-out ${ className={` flex h-full flex-col rounded-lg p-4 mb-0 bg-gray-100 dark:bg-stone-950 z-40 transition-width !ease-in-out justify-between ${
isOpen ? "w-[288px]" : "w-[80px]" isOpen ? "w-[288px]" : "w-[80px]"
}`} }`}
> >
{!isOpen && ( <div>
<div className="w-full flex justify-center items-center"> {!isOpen && (
<button <div className="w-full flex justify-center items-center">
className="w-5 h-5 mb-3 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center" <button
onClick={toggleSidebar} className="w-5 h-5 mb-3 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
> onClick={toggleSidebar}
<ChevronRightIcon /> >
</button> <ChevronRightIcon />
</div> </button>
)} </div>
<div
className={`flex ${
isOpen ? "justify-between" : "justify-center"
} w-full items-center px-2`}
>
<div className="flex flex-row items-center gap-3 font-bold">
<img src="/logohumas.png" className="w-20" />
{/* {isOpen && <span>ACME</span>} */}
</div>
{isOpen && (
<button
className="w-5 h-5 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
onClick={toggleSidebar}
>
<ChevronLeftIcon />
</button>
)} )}
</div> <div
{/* <div className={`flex ${isOpen ? "justify-between" : "justify-center"} w-full items-center px-2 mt-4 mb-4`}> className={`flex ${
<div className='flex flex-row items-center gap-3 font-bold'> isOpen ? "justify-between" : "justify-center"
<Dropdown placement="bottom-start"> } w-full items-center px-2`}
<DropdownTrigger> >
<User <Link
as="button" href="/"
avatarProps={{ className="flex flex-row items-center gap-3 font-bold"
isBordered: true, >
src: "https://i.pravatar.cc/150?u=a042581f4e29026024d", <img src="/logohumas.png" className="w-20" />
}} {/* {isOpen && <span>ACME</span>} */}
classNames={{ </Link>
base: `transition-transform gap-0`, {isOpen && (
wrapper: `${isOpen && "pl-3"}` <button
}} className="w-5 h-5 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
description={isOpen && "@tonyreichert"} onClick={toggleSidebar}
name={isOpen && "Tony Reichert"} >
/> <ChevronLeftIcon />
</DropdownTrigger> </button>
<DropdownMenu aria-label="User Actions" variant="flat"> )}
<DropdownItem key="profile" className="h-14 gap-2"> </div>
<p className="font-bold">Signed in as</p>
<p className="font-bold">@tonyreichert</p> <SidebarMenu>
</DropdownItem> {sideBarDummyData
<DropdownItem key="team_settings">Profile Settings</DropdownItem> ? sideBarDummyData?.map((list: any, index: number) =>
<DropdownItem key="analytics"> list.isGroup ? (
Analytics <p
</DropdownItem> key={list}
<DropdownItem key="help_and_feedback"> className={`font-bold mr-4 ${
Help & Feedback !isOpen ? "text-center" : ""
</DropdownItem> }`}
<DropdownItem key="logout" color="danger"> >
Log Out {isOpen ? list.name : "..."}
</DropdownItem> </p>
</DropdownMenu> ) : list.childMenu?.length < 1 ? (
</Dropdown> <>
</div> {isOpen ? (
</div> */}
<SidebarMenu>
{sideBarDummyData
? sideBarDummyData?.map((list: any, index: number) =>
list.isGroup ? (
<p
key={list}
className={`font-bold mr-4 ${!isOpen ? "text-center" : ""}`}
>
{isOpen ? list.name : "..."}
</p>
) : list.childMenu?.length < 1 ? (
<>
{isOpen ? (
<Link key={list.id} href={list.modulePathUrl}>
<div
className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "text-zinc-600 dark:text-zinc-400"
}`}
>
{list.icon} {isOpen && list.name}
</div>
</Link>
) : (
<Tooltip
content={list.name}
placement="right"
delay={0}
closeDelay={0}
>
<Link key={list.id} href={list.modulePathUrl}> <Link key={list.id} href={list.modulePathUrl}>
<div <div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${ className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold" ? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "text-zinc-600 dark:text-zinc-400" : "text-zinc-600 dark:text-zinc-400"
@ -337,32 +289,53 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
{list.icon} {isOpen && list.name} {list.icon} {isOpen && list.name}
</div> </div>
</Link> </Link>
</Tooltip> ) : (
)} <Tooltip
</> content={list.name}
) : ( placement="right"
<SidebarCollapseItems delay={0}
key={list.id} closeDelay={0}
title={list.name} >
isActive={pathname.includes(list.modulePathUrl)} <Link key={list.id} href={list.modulePathUrl}>
icon={list.icon} <div
items={[ className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
list?.childMenu?.map((item: any) => ( pathname.includes(list.modulePathUrl)
<SidebarCollapseSubItems ? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
key={item.id} : "text-zinc-600 dark:text-zinc-400"
title={item?.name} }`}
isActive={pathname.includes(item.modulePathUrl)} >
isParentActive={pathname.includes(list.modulePathUrl)} {list.icon} {isOpen && list.name}
path={item.modulePathUrl} </div>
icon={item.icon} </Link>
/> </Tooltip>
)), )}
]} </>
/> ) : (
<SidebarCollapseItems
key={list.id}
title={list.name}
isActive={pathname.includes(list.modulePathUrl)}
icon={list.icon}
items={[
list?.childMenu?.map((item: any) => (
<SidebarCollapseSubItems
key={item.id}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
)
) )
) : ""}
: ""} </SidebarMenu>
</SidebarMenu> </div>
<div <div
className={`mt-12 p-2 flex ${ className={`mt-12 p-2 flex ${
isOpen ? "justify-start ml-2" : "justify-center" isOpen ? "justify-start ml-2" : "justify-center"

View File

@ -0,0 +1,189 @@
"use client";
import {
TableCell,
TableRow,
Table,
TableHeader,
TableColumn,
TableBody,
Pagination,
Checkbox,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
Input,
} from "@nextui-org/react";
import { Button } from "@nextui-org/button";
import React, {
Key,
Suspense,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { SearchIcon, UserIcon } from "@/components/icons";
import { close, loading } from "@/config/swal";
import {
getListArticleDraft,
getListTranscript,
} from "@/service/generate-article";
import { textEllipsis } from "@/utils/global";
import { getListArticle } from "@/service/article";
const columns = [
{ name: "No", uid: "no" },
{ name: "Main Keyword", uid: "mainKeyword" },
{ name: "Additional Keywords", uid: "additionalKeywords" },
{ name: "Article", uid: "articleBody" },
];
export default function ArticleDraftTable(props: {
articleContent: (data: string) => void;
}) {
const [selectedTableRow, setSelectedTableRow] = useState<any>(new Set([""]));
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [totalPage, setTotalPage] = useState(1);
const [dataTableTranscript, setDataTableTranscript] = useState<any[]>([]);
useEffect(() => {
initFetch();
}, [page]);
const initFetch = async () => {
loading();
const res = await getListArticleDraft({
query: search,
page: page,
userId: "humasClientIdtest",
limit: 10,
status: [0, 2],
isSingle: true,
createdBy: "123123",
sort: [
{
column: "id",
sort: "desc",
},
],
});
close();
setTotalPage(Math.ceil(res?.data?.total / 10));
const data = res?.data?.data;
const startIndex = 10 * (page - 1);
let iterate = 0;
const newData = data?.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
setDataTableTranscript(newData);
};
const renderCell = useCallback((article: any, columnKey: any) => {
const cellValue = article[columnKey];
switch (columnKey) {
case "articleBody":
return <p>{textEllipsis(cellValue, 200)}</p>;
default:
return cellValue;
}
}, []);
const handleSelected = (e: any) => {
setSelectedTableRow(new Set([e.currentKey]));
const now = findDataTable(e.currentKey);
props.articleContent(now?.articleBody ? now?.articleBody : "");
};
const handleSearch = () => {
initFetch();
};
const findDataTable = (data: any) => {
const temp = dataTableTranscript.find((item: any) => item.id == data);
return temp;
};
return (
<Suspense>
<div className="mt-3 flex flex-row gap-3">
<div className="w-full">
<Input
type="title"
value={search}
onChange={(e) => setSearch(e.target.value)}
label=""
variant="bordered"
placeholder="Search"
labelPlacement="outside"
/>
</div>
<Button color="primary" onPress={handleSearch}>
Search
<SearchIcon />
</Button>
</div>
<div className="mt-4">
<div className="flex flex-col items-center rounded-2xl">
<Table
selectionMode="multiple"
aria-label="transcript-table"
className="rounded-xl"
classNames={{
th: "bg-white text-black border-b-1",
base: "bg-white",
wrapper: "min-h-[50px] bg-transpararent text-black",
}}
checkboxesProps={{
classNames: {
wrapper:
"after:bg-foreground after:text-background text-background",
},
}}
selectedKeys={selectedTableRow}
onSelectionChange={handleSelected}
>
<TableHeader columns={columns}>
{(column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
)}
</TableHeader>
<TableBody
items={dataTableTranscript}
emptyContent={"No data to display."}
>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
)}
</TableBody>
</Table>
<div className="mt-2">
<Pagination
isCompact
showControls
showShadow
color="primary"
classNames={{
base: "bg-transparent",
wrapper: "bg-transparent",
}}
page={page}
total={totalPage}
onChange={(page) => setPage(page)}
/>
</div>
</div>
</div>
</Suspense>
);
}

View File

@ -0,0 +1,192 @@
"use client";
import {
TableCell,
TableRow,
Table,
TableHeader,
TableColumn,
TableBody,
Pagination,
Checkbox,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
Input,
} from "@nextui-org/react";
import { Button } from "@nextui-org/button";
import React, {
Key,
Suspense,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { SearchIcon, UserIcon } from "@/components/icons";
import { close, loading } from "@/config/swal";
import { getListTranscript } from "@/service/generate-article";
import { textEllipsis } from "@/utils/global";
const columns = [
{ name: "No", uid: "no" },
{ name: "Title", uid: "title" },
{ name: "Prompt", uid: "prompt" },
{ name: "Content", uid: "content" },
];
export default function TranscriptDraftTable(props: {
initTranscriptId?: string;
transriptContent: (data: string) => void;
}) {
const [selectedTableRow, setSelectedTableRow] = useState<any>(new Set([""]));
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [totalPage, setTotalPage] = useState(1);
const [dataTableTranscript, setDataTableTranscript] = useState<any[]>([]);
useEffect(() => {
setSelectedTableRow(new Set([props.initTranscriptId]));
const now = findDataTable(Array.from(selectedTableRow)[0]);
props.transriptContent(now?.content ? now?.content : "");
}, [dataTableTranscript, props.initTranscriptId]);
useEffect(() => {
initFetch();
}, [page]);
const initFetch = async () => {
loading();
const res = await getListTranscript({
query: search || "",
page: page,
clientId: "humasClientIdTest",
limit: 10,
createdBy: null,
status: [0, 2],
sort: [
{
column: "id",
sort: "desc",
},
],
});
close();
setTotalPage(Math.ceil(res?.data?.total / 10));
const data = res?.data?.data;
const startIndex = 10 * (page - 1);
let iterate = 0;
const newData = data?.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
setDataTableTranscript(newData);
};
const renderCell = useCallback((article: any, columnKey: any) => {
const cellValue = article[columnKey];
switch (columnKey) {
case "content":
return <p>{textEllipsis(cellValue, 200)}</p>;
default:
return cellValue;
}
}, []);
const handleSelected = (e: any) => {
setSelectedTableRow(new Set([e.currentKey]));
const now = findDataTable(e.currentKey);
props.transriptContent(now?.content ? now?.content : "");
};
const handleSearch = () => {
initFetch();
};
const findDataTable = (data: any) => {
const temp = dataTableTranscript.find((item: any) => item.id == data);
return temp;
};
return (
<Suspense>
<div className="mt-3 flex flex-row gap-3">
<div className="w-full">
<Input
type="title"
value={search}
onChange={(e) => setSearch(e.target.value)}
label=""
variant="bordered"
placeholder="Search"
labelPlacement="outside"
/>
</div>
<Button color="primary" onPress={handleSearch}>
Search
<SearchIcon />
</Button>
</div>
<div className="mt-4">
<div className="flex flex-col items-center rounded-2xl">
<Table
selectionMode="multiple"
aria-label="transcript-table"
className="rounded-xl"
classNames={{
th: "bg-white text-black border-b-1",
base: "bg-white",
wrapper: "min-h-[50px] bg-transpararent text-black",
}}
checkboxesProps={{
classNames: {
wrapper:
"after:bg-foreground after:text-background text-background",
},
}}
selectedKeys={selectedTableRow}
onSelectionChange={handleSelected}
>
<TableHeader columns={columns}>
{(column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
)}
</TableHeader>
<TableBody
items={dataTableTranscript}
emptyContent={"No data to display."}
>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
)}
</TableBody>
</Table>
<div className="mt-2">
<Pagination
isCompact
showControls
showShadow
color="primary"
classNames={{
base: "bg-transparent",
wrapper: "bg-transparent",
}}
page={page}
total={totalPage}
onChange={(page) => setPage(page)}
/>
</div>
</div>
</div>
</Suspense>
);
}

View File

@ -34,7 +34,7 @@ export const Breadcrumb = () => {
}; };
return ( return (
<div className="h-[100px] gap-0 grid rounded-lg border-small"> <div className="h-[100px] rounded-lg border-small">
<div className="px-4 md:px-8"> <div className="px-4 md:px-8">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
<div className="pt-1"> <div className="pt-1">

31
package-lock.json generated
View File

@ -59,7 +59,8 @@
"tailwind-variants": "^0.1.18", "tailwind-variants": "^0.1.18",
"tailwindcss": "3.3.5", "tailwindcss": "3.3.5",
"typescript": "5.0.4", "typescript": "5.0.4",
"zod": "^3.23.8" "zod": "^3.23.8",
"zustand": "^5.0.1"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -7290,6 +7291,34 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
},
"node_modules/zustand": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",
"integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View File

@ -60,6 +60,7 @@
"tailwind-variants": "^0.1.18", "tailwind-variants": "^0.1.18",
"tailwindcss": "3.3.5", "tailwindcss": "3.3.5",
"typescript": "5.0.4", "typescript": "5.0.4",
"zod": "^3.23.8" "zod": "^3.23.8",
"zustand": "^5.0.1"
} }
} }

190
service/generate-article.ts Normal file
View File

@ -0,0 +1,190 @@
import { httpGet, httpPost } from "@/services/http-config/disestages-services";
interface GenerateKeywordsAndTitleRequestData {
keyword: string;
style: string;
website: string;
connectToWeb: boolean;
lang: string;
pointOfView: string;
clientId: string;
}
interface GenerateArticleRequest {
advConfig: string;
style: string;
website: string;
connectToWeb: boolean;
lang: string;
pointOfView: string;
title: string;
imageSource: string;
mainKeyword: string;
additionalKeywords: string;
targetCountry: null;
articleSize: string;
projectId: number;
createdBy: string;
clientId: string;
}
type BulkArticleRequest = {
style: string;
website: string;
connectToWeb: boolean;
lang: string;
pointOfView: string;
imageSource: string;
targetCountry: null;
articleSize: string;
projectId: number;
data: { title: string; mainKeyword: string; additionalKeywords: string }[];
createdBy: string;
clientId: string;
};
interface ContentRewriteRequest {
advConfig: string;
context: string | null;
style: string;
sentiment: string;
clientId: string;
createdBy: string;
contextType: string;
urlContext: string | null;
lang: string;
}
export type ContentBankRequest = {
query: string;
page: number;
userId: string;
limit: number;
status: number[];
isSingle: boolean;
createdBy: string;
sort: { column: string; sort: string }[];
};
export async function getGenerateTitle(
data: GenerateKeywordsAndTitleRequestData
) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-title", headers, data);
}
export async function getGenerateKeywords(
data: GenerateKeywordsAndTitleRequestData
) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-keywords", headers, data);
}
export async function generateDataArticle(data: GenerateArticleRequest) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-article", headers, data);
}
export async function approveArticle(props: { id: number[] }) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/approve-article", headers, props);
}
export async function rejectArticle(props: { id: number[] }) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/reject-article", headers, props);
}
export async function getGenerateTopicKeywords(data: {
keyword: string;
count: number;
}) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-topic-keywords", headers, data);
}
export async function saveBulkArticle(data: BulkArticleRequest) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-bulk-article", headers, data);
}
export async function getDetailArticle(id: number) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/article/findArticleById/${id}`, headers);
}
export async function generateSpeechToText(data: any) {
const headers = {
"content-type": "multipart/form-data",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/speech-to-text", headers, data);
}
export async function getTranscriptById(id: number) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/speech-to-text/findById/${id}`, headers);
}
export async function getGenerateRewriter(data: ContentRewriteRequest) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/create-rewriter", headers, data);
}
export async function getListTranscript(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/speech-to-text/datatable", headers, data);
}
export async function getListArticleDraft(data: ContentBankRequest) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/article/datatable", headers, data);
}

View File

@ -0,0 +1,12 @@
import axios from "axios";
const baseURL = "https://disestages.com/api";
const axiosDisestagesInstance = axios.create({
baseURL,
headers: {
"content-type": "application/json",
},
});
export default axiosDisestagesInstance;

View File

@ -0,0 +1,47 @@
import axiosDisestagesInstance from "./disestages-instance";
export async function httpPost(pathUrl: any, headers: any, data?: any) {
const response = await axiosDisestagesInstance
.post(pathUrl, data, { headers })
.catch(function (error) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function httpGet(pathUrl: any, headers: any) {
const response = await axiosDisestagesInstance
.get(pathUrl, { headers })
.catch(function (error) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}

View File

@ -0,0 +1,32 @@
import { create } from "zustand";
interface targetStore {
articleIds: { singleArticle: number[]; bulkArticle: number[] };
setArticleIds: (newTarget: {
singleArticle: number[];
bulkArticle: number[];
}) => void;
}
const getInitialTarget = () => {
if (typeof localStorage !== "undefined") {
const stored = localStorage.getItem("generated-article");
const initial = stored
? JSON.parse(stored)
: { singleArticle: [], bulkArticle: [] };
return initial;
}
};
const generatedArticleIds = create<targetStore>((set) => ({
articleIds: getInitialTarget(),
setArticleIds: (newTarget: {
singleArticle: number[];
bulkArticle: number[];
}) => {
localStorage.setItem("generated-article", JSON.stringify(newTarget));
set({ articleIds: newTarget });
},
}));
export default generatedArticleIds;

View File

@ -70,3 +70,25 @@ const LoadScript = () => {
}; };
export default LoadScript; export default LoadScript;
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function textEllipsis(
str: string,
maxLength: number,
{ side = "end", ellipsis = "..." } = {}
) {
if (str !== undefined && str?.length > maxLength) {
switch (side) {
case "start":
return ellipsis + str.slice(-(maxLength - ellipsis.length));
case "end":
default:
return str.slice(0, maxLength - ellipsis.length) + ellipsis;
}
}
return str;
}