This commit is contained in:
hanif salafi 2025-01-01 19:26:31 +07:00
commit 608afb32c2
9 changed files with 522 additions and 86 deletions

View File

@ -12,32 +12,22 @@ import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Plus } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { CalendarCategory } from "./data";
import { EventContentArg } from "@fullcalendar/core";
import EventModal from "./event-modal";
import { useTranslations } from "next-intl";
import { getAgendaSettingsList } from "@/service/agenda-setting/agenda-setting";
import dayjs from "dayjs";
import { getCookiesDecrypt } from "@/lib/utils";
import { CalendarCategory } from "./data";
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
interface CalendarViewProps {
events: CalendarEvent[];
// events: CalendarEvent[];
categories: CalendarCategory[];
}
const INITIAL_YEAR = dayjs().format("YYYY");
const INITIAL_MONTH = dayjs().format("M");
export interface CalendarEvent {
id: string;
title: string;
start: Date;
end: Date;
allDay: boolean;
extendedProps: {
calendar: string;
description?: string;
};
}
export interface AgendaSettingsAPIResponse {
id: number;
@ -59,14 +49,11 @@ interface APIResponse {
data: AgendaSettingsAPIResponse[] | null; // `data` bisa berupa array atau null
}
const CalendarView = ({ events, categories }: CalendarViewProps) => {
const CalendarView = ({ categories }: CalendarViewProps) => {
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
null
);
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
// const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
// null
// );
const roleId = Number(getCookiesDecrypt("urie")) || 0;
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
const [loading, setLoading] = useState<boolean>(false);
@ -84,36 +71,47 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
{ title: "Create New theme", id: "104", tag: "etc" },
]);
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
useEffect(() => {
getCalendarEvents();
});
}, []);
const getCalendarEvents = async () => {
const res = await getAgendaSettingsList(INITIAL_YEAR, INITIAL_MONTH, "");
console.log("ress", res);
console.log("API Response:", res);
if (res.error) {
return false;
return;
}
// Map API data to the calendarEvents structure
const events = res?.data?.data.map((event: any) => ({
id: event.id.toString(),
title: event.title,
start: new Date(event.startDate),
end: new Date(event.endDate),
allDay: true, // Assuming all events are all-day by default
extendedProps: {
calendar: event.agendaType, // Map agendaType to the calendar category
description: event.description,
},
}));
setCalendarEvents(events);
console.log("event", events);
};
useEffect(() => {
setSelectedCategory(categories?.map((c) => c.value));
}, [events, categories]);
}, [categories]);
useEffect(() => {
console.log("Fetched events from API:", apiEvents);
}, [apiEvents]);
const filteredEvents = apiEvents?.filter((event) =>
const filteredEvents = calendarEvents?.filter((event) =>
selectedCategory?.includes(event.extendedProps.calendar)
);
const displayedEvents =
filteredEvents?.length > 0 ? filteredEvents : apiEvents;
useEffect(() => {
console.log("Filtered events based on category:", displayedEvents);
}, [filteredEvents, apiEvents]);
filteredEvents?.length > 1 ? filteredEvents : apiEvents;
useEffect(() => {
setSelectedCategory(categories?.map((c) => c.value));
@ -151,6 +149,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
description: item.description,
},
}));
setApiEvents(eventsFromAPI);
} else {
console.warn("No events found in API response.");
@ -167,6 +166,14 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
fetchAgendaEvents();
}, []);
useEffect(() => {
console.log("Fetched events from API 1:", apiEvents);
}, [apiEvents]);
useEffect(() => {
console.log("Filtered events based on category 1:", calendarEvents);
}, [filteredEvents, apiEvents]);
useEffect(() => {
const draggableEl = document.getElementById("external-events");
@ -247,17 +254,16 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
<CardContent className="p-0">
{roleId === 11 && (
<CardHeader className="border-none mb-2 pt-5">
<Button
onClick={handleDateClick}
className="dark:bg-background dark:text-foreground"
>
<Plus className="w-4 h-4 me-1" />
{"Tambahkan Agenda baru"}
</Button>
</CardHeader>
)}
<CardHeader className="border-none mb-2 pt-5">
<Button
onClick={handleDateClick}
className="dark:bg-background dark:text-foreground"
>
<Plus className="w-4 h-4 me-1" />
{"Tambahkan Agenda baru"}
</Button>
</CardHeader>
<div className="px-3">
<Calendar
mode="single"
@ -321,7 +327,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
headerToolbar={{
left: "prev,next today",
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
right: "dayGridMonth,listWeek,",
}}
events={displayedEvents}
editable={true}
@ -352,3 +358,15 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
};
export default CalendarView;
export type CalendarEvent = {
id: string;
title: string;
start: Date;
end: Date;
allDay: boolean;
extendedProps: {
calendar: string;
description: string;
};
};

View File

@ -13,15 +13,6 @@ const nextMonth = date.getMonth() === 11 ? new Date(date.getFullYear() + 1, 0, 1
// prettier-ignore
const prevMonth = date.getMonth() === 11 ? new Date(date.getFullYear() - 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() - 1, 1)
export const getCalendarEvents = async () => {
const res = await getAgendaSettingsList(INITIAL_YEAR, INITIAL_MONTH, "");
if (res.error) {
return false;
}
console.log("ress", res.data.data);
return res?.data?.data;
};
export const calendarEvents = [
{
id: faker.string.uuid(),
@ -94,7 +85,7 @@ export const calendarEvents = [
export const calendarCategories = [
{
label: "Nasional",
value: "national",
value: "mabes",
activeClass: "ring-primary-500 bg-primary-500",
className: "group-hover:border-blue-500",
},
@ -127,7 +118,7 @@ export const calendarCategories = [
export const categories = [
{
label: "Nasional",
value: "national",
value: "mabes",
className:
"data-[state=checked]:bg-primary data-[state=checked]:ring-primary",
},

View File

@ -135,7 +135,7 @@ const EventModal = ({
const reqData = {
title: data.title,
description: data.description || "",
description: data.description,
agendaType: calendarProps,
startDate: format(startDate, "yyyy-MM-dd"),
endDate: format(endDate, "yyyy-MM-dd"),
@ -162,7 +162,7 @@ const EventModal = ({
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/contributor/agenda-setting");
router.push("en/contributor/agenda-setting");
});
};
@ -272,11 +272,18 @@ const EventModal = ({
<div className="space-y-4 pb-5 ">
<div className="space-y-1.5">
<Label htmlFor="title">Judul Agenda</Label>
<Input
id="title"
type="text"
placeholder="Enter Event Name"
{...register("title")}
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors?.title?.message && (
<div className="text-destructive text-sm">
@ -475,10 +482,16 @@ const EventModal = ({
)}
<div className="space-y-1.5">
<Label htmlFor="description">Isi Agenda Setting</Label>
<Textarea
id="description"
placeholder="Enter Event Name"
{...register("description")}
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
defaultValue={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors?.description?.message && (
<div className="text-destructive text-sm">

View File

@ -1,5 +1,5 @@
import { getEvents, getCategories } from "./utils";
import { Category } from "./data";
import { calendarEvents, Category } from "./data";
import CalendarView from "./calender-view";
const CalenderPage = async () => {
@ -11,7 +11,7 @@ const CalenderPage = async () => {
}));
return (
<div>
<CalendarView events={events} categories={formattedCategories} />
<CalendarView categories={formattedCategories} />
</div>
);
};

View File

@ -63,12 +63,12 @@ const ReactTableImagePage = () => {
Unggah Foto
</Button>
</Link>
<Link href={"/contributor/content/image/createAi"}>
{/* <Link href={"/contributor/content/image/createAi"}>
<Button color="primary" className="text-white ml-3">
<UploadIcon />
Unggah Foto Dengan AI
</Button>
</Link>
</Link> */}
</div>
</div>
</CardTitle>

View File

@ -31,6 +31,14 @@ import {
uploadThumbnail,
} from "@/service/content/content";
import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/content/ai";
import { getCookiesDecrypt } from "@/lib/utils";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -56,12 +64,33 @@ export default function FormImage() {
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
@ -82,6 +111,161 @@ export default function FormImage() {
resolver: zodResolver(imageSchema),
});
const doGenerateMainKeyword = async () => {
console.log(selectedMainKeyword);
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error during generation process:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateTitle = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
} catch (error) {
console.error("Error generating title:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateKeyword = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error generating keywords:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const handleGenerateArtikel = async () => {
const request = {
advConfig: selectedAdvConfig,
style: selectedWritingStyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
title: title,
imageSource: "Web",
mainKeyword: selectedMainKeyword,
additionalKeywords: selectedSEO,
targetCountry: null,
articleSize: selectedSize,
projectId: 2,
createdBy: roleId,
clientId: "ngDLPPiorplznw2jTqVe3YFCz5xqKfUJ",
};
const res = await generateDataArticle(request);
close();
if (res.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[4] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
};
const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id);
const articleData = res?.data?.data;
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
};
// const handleKeyDown = (e: any) => {
// const newTag = e.target.value.trim(); // Ambil nilai input
// if (e.key === "Enter" && newTag) {
@ -242,6 +426,7 @@ export default function FormImage() {
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
@ -268,7 +453,228 @@ export default function FormImage() {
</Select>
</div>
</div>
<div className="py-3">
<div className="flex flex-row items-center gap-3 py-2">
<Label>Bantuan AI</Label>
<div className="flex items-center gap-3">
<Switch
defaultChecked={isSwitchOn}
color="primary"
id="c2"
onCheckedChange={(checked: boolean) =>
setIsSwitchOn(checked)
}
/>
</div>
</div>
{isSwitchOn && (
<div>
<div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label>
<Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="id">Indonesia</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label>
<Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="friendly">Friendly</SelectItem>
<SelectItem value="profesional">
Profesional
</SelectItem>
<SelectItem value="informational">
Informational
</SelectItem>
<SelectItem value="neutral">Neutral</SelectItem>
<SelectItem value="witty">Witty</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label>
<Select onValueChange={setSelectedSize}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="news">
News (300 - 900 words)
</SelectItem>
<SelectItem value="info">
Info (900 - 2000 words)
</SelectItem>
<SelectItem value="detail">
Detail (2000 - 5000 words)
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateMainKeyword}
disabled={isLoading}
>
{isLoading ? "Processing..." : "Proses"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={selectedMainKeyword}
onChange={(e) => setSelectedMainKeyword(e.target.value)}
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateTitle}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Generated Title"
/>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label>
<Button
variant={"outline"}
color="primary"
onClick={doGenerateKeyword}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<p className="font-semibold">
Kata kunci untuk disertakan dalam teks
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci
utama untuk setiap bagian dan menggunakannya untuk membuat
artikel. Untuk menambahkan kata kunci baru, ketik ', +
kata kunci'.
</p>
<div className="mt-3">
<Textarea
value={selectedSEO}
onChange={(e) => setSelectedSEO(e.target.value)}
placeholder="Enter Title"
/>
</div>
</div>
<div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label>
<div className="mt-3">
<Controller
control={control}
name="title"
render={({ field }) => (
<Textarea
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
</div>
</div>
<div>
<div className="my-5">
<Button
// variant={"outline"}
color="primary"
onClick={handleGenerateArtikel}
>
Generate Article
</Button>
</div>
{isGeneratedArticle && (
<div className="mt-3 pb-0">
{articleIds.map((id: string, index: number) => (
<Button
key={index}
className={`btn m-1 ${
selectedArticleId === id
? "btn-warning"
: "btn-success"
}`}
onClick={() => handleArticleIdClick(id)}
variant={"outline"}
color="success"
>
{id}
</Button>
))}
</div>
)}
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<a
href={`/admin/media/${
fileTypeId === "1"
? "image"
: fileTypeId === "2"
? "video"
: fileTypeId === "3"
? "text"
: "audio"
}/update-new/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
Edit
</Button>
</a>
)}
</div>
</div>
</div>
</div>
)}
<div className="">
<Label>Deskripsi</Label>
<Controller
control={control}
@ -276,7 +682,7 @@ export default function FormImage() {
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
value={articleBody || value}
onChange={onChange}
className="dark:text-black"
/>

View File

@ -351,6 +351,14 @@ export default function FormConvertSPIT() {
</p>
)}
</div>
<div className="my-5">
<Button
// variant={"outline"}
color="primary"
>
Content Rewrite
</Button>
</div>
<div>
<Label className="text-xl text-black">File Media</Label>
<div className="w-full ">

View File

@ -189,10 +189,10 @@ export default function FormContestDetail() {
theme: data.theme,
};
const response = await createTask(requestData);
// const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
// console.log("response", response);
MySwal.fire({
title: "Sukses",
@ -201,7 +201,7 @@ export default function FormContestDetail() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
router.push("/en/shared/contest");
});
};
@ -369,11 +369,11 @@ export default function FormContestDetail() {
</div>
</div>
{/* <div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div> */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
</div>
</Card>

View File

@ -97,13 +97,13 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "heroicons:credit-card",
children: [],
},
{
href: "/contributor/content/nulis-ai",
label: "nulis ai",
active: pathname.includes("/content/nulisai"),
icon: "heroicons:credit-card",
children: [],
},
// {
// href: "/contributor/content/nulis-ai",
// label: "nulis ai",
// active: pathname.includes("/content/nulisai"),
// icon: "heroicons:credit-card",
// children: [],
// },
],
},
],