feat:content rewrite create article, fix landing

This commit is contained in:
Rama Priyanto 2025-02-04 15:31:04 +07:00
parent 459ba05261
commit eb1bd6903f
11 changed files with 482 additions and 55 deletions

View File

@ -21,7 +21,13 @@ import {
} from "@/service/article";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import { Checkbox, Chip } from "@nextui-org/react";
import {
Checkbox,
Chip,
Select,
SelectItem,
SelectSection,
} from "@nextui-org/react";
import GenerateSingleArticleForm from "./generate-ai-single-form";
import { htmlToString } from "@/utils/global";
import { close, error, loading } from "@/config/swal";
@ -32,6 +38,7 @@ import {
saveManualContext,
updateManualArticle,
} from "@/service/generate-article";
import GenerateContentRewriteForm from "./generate-ai-content-rewrite-form";
const CustomEditor = dynamic(
() => {
@ -99,6 +106,7 @@ export default function CreateArticleForm() {
);
const [thumbnailValidation, setThumbnailValidation] = useState("");
const [diseData, setDiseData] = useState<DiseData>();
const [selectedWritingType, setSelectedWritingType] = useState("single");
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
@ -108,6 +116,9 @@ export default function CreateArticleForm() {
]);
},
multiple: true,
accept: {
"image/*": [],
},
});
const formOptions = {
@ -475,6 +486,34 @@ export default function CreateArticleForm() {
</Switch>
{useAi && (
<div className="flex flex-col gap-2">
<Select
label="Writing Style"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedWritingType]}
onChange={(e) =>
e.target.value !== ""
? setSelectedWritingType(e.target.value)
: ""
}
className="w-full"
classNames={{
label: "!text-black",
value: "!text-black",
trigger: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
>
<SelectSection>
<SelectItem key="single">Single Article</SelectItem>
<SelectItem key="rewrite">Content Rewrite</SelectItem>
</SelectSection>
</Select>
{selectedWritingType === "single" ? (
<GenerateSingleArticleForm
content={(data) => {
setDiseData(data);
@ -484,6 +523,18 @@ export default function CreateArticleForm() {
);
}}
/>
) : (
<GenerateContentRewriteForm
content={(data) => {
setDiseData(data);
setValue(
"description",
data?.articleBody ? data?.articleBody : ""
);
}}
/>
)}
</div>
)}
<p className="text-sm mt-3">Deskripsi</p>
@ -575,6 +626,7 @@ export default function CreateArticleForm() {
type="file"
multiple
className="w-fit h-fit"
accept="image/*"
onChange={handleFileChange}
/>
{thumbnailValidation !== "" && (

View File

@ -121,6 +121,9 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
]);
},
multiple: true,
accept: {
"image/*": [],
},
});
const formOptions = {
@ -640,6 +643,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
type="file"
multiple
className="w-fit h-fit"
accept="image/*"
onChange={handleFileChange}
/>
{thumbnailValidation !== "" && (

View File

@ -0,0 +1,280 @@
"use client";
import {
Button,
Input,
Select,
SelectItem,
SelectSection,
} from "@nextui-org/react";
import { FormEvent, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { close, error, loading } from "@/config/swal";
import {
generateDataArticle,
getDetailArticle,
getGenerateKeywords,
getGenerateRewriter,
getGenerateTitle,
} from "@/service/generate-article";
import { delay } from "@/utils/global";
import dynamic from "next/dynamic";
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
const writingStyle = [
{
id: 1,
name: "Friendly",
},
{
id: 1,
name: "Professional",
},
{
id: 3,
name: "Informational",
},
{
id: 4,
name: "Neutral",
},
{
id: 5,
name: "Witty",
},
];
const articleSize = [
{
id: 1,
name: "News (300 - 900 words)",
value: "News",
},
{
id: 2,
name: "Info (900 - 2000 words)",
value: "Info",
},
{
id: 3,
name: "Detail (2000 - 5000 words)",
value: "Detail",
},
];
interface DiseData {
id: number;
articleBody: string;
title: string;
metaTitle: string;
description: string;
metaDescription: string;
mainKeyword: string;
additionalKeywords: string;
}
export default function GenerateContentRewriteForm(props: {
content: (data: DiseData) => void;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
const [selectedLanguage, setSelectedLanguage] = useState("id");
const [mainKeyword, setMainKeyword] = useState("");
const [articleIds, setArticleIds] = useState<number[]>([]);
const [selectedId, setSelectedId] = useState<number>();
const [isLoading, setIsLoading] = useState(true);
const onSubmit = async () => {
loading();
const request = {
advConfig: "",
context: mainKeyword,
style: selectedWritingSyle,
sentiment: "Informational",
urlContext: null,
contextType: "article",
lang: selectedLanguage,
createdBy: "123123",
clientId: "humasClientIdtest",
};
const res = await getGenerateRewriter(request);
close();
if (res?.error) {
error("Error");
}
setArticleIds([...articleIds, res?.data?.data?.id]);
};
useEffect(() => {
getArticleDetail();
}, [selectedId]);
const checkArticleStatus = async (data: string | null) => {
if (data === null) {
delay(7000).then(() => {
getArticleDetail();
});
}
};
const getArticleDetail = async () => {
if (selectedId) {
const res = await getDetailArticle(selectedId);
const data = res?.data?.data;
checkArticleStatus(data?.articleBody);
if (data?.articleBody !== null) {
setIsLoading(false);
props.content(data);
} else {
setIsLoading(true);
props.content({
id: data?.id,
articleBody: "",
title: "",
metaTitle: "",
description: "",
metaDescription: "",
additionalKeywords: "",
mainKeyword: "",
});
}
}
};
return (
<fieldset>
<form className="flex flex-col w-full mt-3">
<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"
classNames={{
label: "!text-black",
value: "!text-black",
trigger: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
>
<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"
classNames={{
label: "!text-black",
value: "!text-black",
trigger: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
>
<SelectSection>
{articleSize.map((size) => (
<SelectItem key={size.value}>{size.name}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Bahasa"
variant="bordered"
labelPlacement="outside"
placeholder=""
selectedKeys={[selectedLanguage]}
onChange={(e) =>
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
}
className="w-full"
classNames={{
label: "!text-black",
value: "!text-black",
trigger: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
>
<SelectSection>
<SelectItem key="id">Indonesia</SelectItem>
<SelectItem key="en">English</SelectItem>
</SelectSection>
</Select>
</div>
<div className="flex flex-col mt-3">
<div className="flex flex-row gap-2 items-center">
<p className="text-sm">Text</p>
</div>
<div className="w-[78vw] lg:w-full">
<CustomEditor onChange={setMainKeyword} initialData={mainKeyword} />
</div>
{mainKeyword == "" && (
<p className="text-red-400 text-sm">Required</p>
)}
<Button
color="primary"
className="my-5 w-full py-5 text-xs md:text-base"
type="button"
onPress={onSubmit}
isDisabled={mainKeyword == ""}
>
Generate
</Button>
</div>
{articleIds.length > 0 && (
<div className="flex flex-row gap-1">
{articleIds?.map((id) => (
<Button
onPress={() => setSelectedId(id)}
key={id}
isLoading={isLoading && selectedId == id}
color={
selectedId == id && isLoading
? "warning"
: selectedId == id
? "success"
: "default"
}
>
<p className={selectedId == id ? "text-white" : "text-black"}>
{id}
</p>
</Button>
))}
</div>
)}
</form>
</fieldset>
);
}

View File

@ -388,7 +388,7 @@ export default function GenerateSingleArticleForm(props: {
</div>
{articleIds.length > 0 && (
<div className="flex flex-row gap-1">
{articleIds?.map((id) => (
{articleIds?.map((id, index) => (
<Button
onPress={() => setSelectedId(id)}
key={id}
@ -402,7 +402,7 @@ export default function GenerateSingleArticleForm(props: {
}
>
<p className={selectedId == id ? "text-white" : "text-black"}>
{id}
Article {index}
</p>
</Button>
))}

View File

@ -13,7 +13,7 @@ export default function BodyLayout() {
<div className="lg:w-[75%] space-y-7">
<CategorySatker />
<RegionalNews />
{/* <MedolUpdate /> */}
<MedolUpdate />
<MediaSocial />
<ENewsPolri />
</div>

View File

@ -53,6 +53,12 @@ export default function CategorySatker() {
title: "Itwasum",
path: "/news/itwasum",
},
{
id: 6,
img: "/assets/satker2/stik-ptik.svg",
title: "STIK-PTIK",
path: "/news/stik-ptik",
},
];
const SatkerAll = [
@ -305,14 +311,14 @@ export default function CategorySatker() {
return (
<div className="text-center bg-[#DD8306] rounded-none md:rounded-lg h-auto lg:h-[338px] space-y-0 py-4 md:space-y-7 flex flex-col justify-center">
<div className="text-white font-bold text-2xl underline underline-offset-4 decoration-red-600">
<div className="text-xl text-white w-full justify-center flex">
<p className="border-b-3 border-[#C3170F] py-2 w-fit">
{" "}
{t("kategoriSatker")}
</p>
</div>
<div className="flex items-center justify-around">
<div>
<ChevronLeftWhite />
</div>
<div className="gap-2 md:gap-4 lg:gap-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
<div className="gap-2 md:gap-4 lg:gap-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6">
{list.map((item: any, index: any) => (
<Link
href={`/news/all?satker=${changeNameToSlug(item.title)}`}
@ -328,9 +334,6 @@ export default function CategorySatker() {
</Link>
))}
</div>
<div>
<ChevronRightWhite />
</div>
</div>
<div>
<Button
@ -355,8 +358,12 @@ export default function CategorySatker() {
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col text-[#DD8306] items-center min-h text-3xl font-semibold">
<ModalHeader className="flex flex-col text-black items-center min-h text-3xl font-semibold">
<div className="text-xl text-black w-full justify-center flex">
<p className="border-b-3 border-[#C3170F] py-2 w-fit">
{t("kategoriSatker")}
</p>
</div>
</ModalHeader>
<ModalBody className="flex flex-row flex-wrap justify-center text-center">
{SatkerAll.map((item: any, index: any) => (
@ -376,7 +383,7 @@ export default function CategorySatker() {
src={item.img}
/>
</Link>
<p className="text-xs font-bold text-[#DD8306] pt-2">
<p className="text-xs font-bold text-black pt-2">
{item.title}
</p>
</div>

View File

@ -115,12 +115,16 @@ export default function HeaderNews() {
className="text-xs text-left m-2 p-2 dark:bg-[#1E1616] bg-white rounded-md flex flex-row gap-2"
key={data.id}
>
<img
<Image
height={480}
width={480}
alt="headernews"
src={
data?.thumbnailUrl == "" ? "no-image.jpg" : data?.thumbnailUrl
data?.thumbnailUrl == ""
? "/no-image.jpg"
: data?.thumbnailUrl
}
className="object-cover w-[18%] rounded-md"
className="object-cover w-[60px] h-[60px] rounded-md"
/>
<div>
<Link

View File

@ -91,9 +91,31 @@ export default function MedolUpdate() {
navigation={true}
modules={[Navigation, Pagination]}
spaceBetween={40}
slidesPerView={2}
slidesPerView={1}
breakpoints={{
// When the window width is less than 640px
720: {
slidesPerView: 2, // Set slidesPerView to 1 on mobile
},
}}
pagination={true}
className="mySwiper"
onSwiper={(swiper) => {
swiper.navigation.nextEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
swiper.navigation.prevEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
}}
>
{mediahubUpdate?.map((newsItem: any) => (
<SwiperSlide key={newsItem.title}>
@ -108,7 +130,7 @@ export default function MedolUpdate() {
radius="lg"
width="300%"
alt="tes"
className="object-cover h-[270px]"
className="object-cover !h-[30vh]"
src={newsItem.thumbnailLink}
/>
</CardBody>
@ -141,9 +163,31 @@ export default function MedolUpdate() {
navigation={true}
modules={[Navigation, Pagination]}
spaceBetween={40}
slidesPerView={2}
slidesPerView={1}
breakpoints={{
// When the window width is less than 640px
720: {
slidesPerView: 2, // Set slidesPerView to 1 on mobile
},
}}
pagination={true}
className="mySwiper"
onSwiper={(swiper) => {
swiper.navigation.nextEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
swiper.navigation.prevEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
}}
>
{tbnUpdate?.map((newsItem: any) => (
<SwiperSlide key={newsItem.title}>
@ -158,7 +202,7 @@ export default function MedolUpdate() {
radius="lg"
width="300%"
alt="tes"
className="object-cover h-[270px]"
className="object-cover !h-[30vh]"
src={newsItem?.image}
/>
</CardBody>
@ -189,9 +233,31 @@ export default function MedolUpdate() {
navigation={true}
modules={[Navigation, Pagination]}
spaceBetween={40}
slidesPerView={2}
slidesPerView={1}
breakpoints={{
// When the window width is less than 640px
720: {
slidesPerView: 2, // Set slidesPerView to 1 on mobile
},
}}
pagination={true}
className="mySwiper"
onSwiper={(swiper) => {
swiper.navigation.nextEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
swiper.navigation.prevEl?.classList.add(
"bg-white/70",
"!text-black",
"rounded-full",
"!w-[40px]",
"!h-[40px]"
);
}}
>
{inpUpdate?.map((newsItem: any) => (
<SwiperSlide key={newsItem?.id}>
@ -206,7 +272,7 @@ export default function MedolUpdate() {
radius="lg"
width="300%"
alt="tes"
className="object-cover h-[270px]"
className="object-cover !h-[30vh]"
src={newsItem.image}
/>
</CardBody>

View File

@ -135,9 +135,11 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
</p>
</div>
<div className="flex justify-center my-2 lg:my-5">
<img
alt="NextUI hero Image"
src={`http://38.47.180.165:8802${data?.files[imageNow]?.file_url}`}
<Image
width={1440}
height={1080}
alt="Main Image"
src={data?.files[imageNow]?.file_url}
className="object-cover w-[100%] rounded-md"
/>
</div>
@ -149,10 +151,12 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
onClick={() => setImageNow(index)}
className="cursor-pointer"
>
<img
<Image
width={480}
height={480}
alt="NextUI hero Image"
src={`http://38.47.180.165:8802${file?.file_url}`}
className="object-cover h-[50px] lg:h-[100px] rounded-md"
src={file?.file_url}
className="object-cover w-[75px] lg:w-[150px] h-[50px] lg:h-[100px] rounded-md"
/>
</a>
))}

View File

@ -273,16 +273,22 @@ export default function ArticleTable() {
label=""
variant="bordered"
labelPlacement="outside"
placeholder="Select"
placeholder="Kategori"
selectionMode="multiple"
selectedKeys={[selectedCategories]}
selectedKeys={selectedCategories}
className="w-full"
items={categories}
classNames={{ trigger: "border-1" }}
onChange={(e) => {
e.target.value === ""
? ""
: setSelectedCategories(e.target.value);
console.log("eeess", e.target.value);
onSelectionChange={setSelectedCategories}
renderValue={(items) => {
return items.map((item) => (
<span
key={item.props?.value}
className="text-black text-xs"
>
{item.textValue},
</span>
));
}}
>
{categories?.map((category: any) => (

View File

@ -4,8 +4,12 @@ const nextConfig = {
ignoreDuringBuilds: true,
},
images: {
domains: ['38.47.180.165'],
remotePatterns: [
{
hostname: "*",
},
}
],
},
};
module.exports = nextConfig
module.exports = nextConfig;