feat: add content-management section

This commit is contained in:
sabdayagra 2024-12-30 23:11:34 +07:00
commit ddaa43c25d
23 changed files with 2713 additions and 189 deletions

View File

@ -320,7 +320,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
}}
events={displayedEvents} // Use apiEvents here
events={displayedEvents}
editable={true}
rerenderDelay={10}
eventDurationEditable={false}

View File

@ -0,0 +1,15 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageAI from "@/components/form/content/image-ai-form";
const ImageAICreatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageAI />
</div>
</div>
);
};
export default ImageAICreatePage;

View File

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

View File

@ -0,0 +1,122 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "title",
header: "Title",
cell: ({ row }) => (
<span className="whitespace-normal">{row.getValue("title")}</span>
),
},
{
accessorKey: "categoryName",
header: "Category",
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
},
{
accessorKey: "createdAt",
header: "Upload Date",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "tags",
header: "Tag",
cell: ({ row }) => <span className="">{row.getValue("tags")}</span>,
},
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
};
// Mengambil `statusName` dari data API
const status = row.getValue("statusName") as string;
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
// Gunakan `statusName` untuk pencocokan
const statusStyles =
statusColors[statusName] || "bg-gray-100 text-gray-600";
return (
<Badge className={cn("rounded-full px-5", statusStyles)}>
{status} {/* Tetap tampilkan nilai asli */}
</Badge>
);
},
},
{
id: "actions",
accessorKey: "action",
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/contributor/blog/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
<Link href={`/contributor/blog/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
<Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
export default columns;

View File

@ -0,0 +1,138 @@
"use client";
import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { getNotifications } from "@/service/notifications/notifications";
import {
CalendarCheck,
CheckCheck,
ChevronLeft,
ChevronRight,
CircleAlert,
Clock7,
MessageCircle,
SquareCheck,
UploadIcon,
} from "lucide-react";
import { useRouter } from "next/navigation";
export type Notification = {
id: number;
notificationTypeId: number;
message: string;
createdAt: string;
isActive: boolean;
isPublic: boolean;
isRead: boolean;
redirectUrl: string;
userGroupIdDst: string | null;
userIdDst: string | null;
userLevelIdDst: string;
userLevelNumberDst: string | null;
userRoleIdDst: string;
};
const getNotificationIcon = (notificationTypeId: number) => {
switch (notificationTypeId) {
case 2:
return <MessageCircle className="h-8 w-8 text-success" />;
case 3:
return <UploadIcon className="h-5 w-5 text-warning" />;
case 4:
return <CheckCheck className="h-5 w-5 text-primary" />;
case 5:
return <SquareCheck className="h-5 w-5 text-warning" />;
case 6:
return <CalendarCheck className="h-5 w-5 text-danger" />;
case 7:
return <CircleAlert className="h-5 w-5 text-danger" />;
case 8:
return <Clock7 className="h-5 w-5 text-danger" />;
default:
return <SquareCheck className="h-5 w-5 text-danger" />;
}
};
const NotificationsList: React.FC = () => {
const router = useRouter();
const [notifications, setNotifications] = useState<Notification[]>([]);
const [totalData, setTotalData] = useState<number>(0);
const [page, setPage] = useState<number>(1);
const [pageSize] = useState<number>(10);
const [totalPage, setTotalPage] = useState<number>(1);
useEffect(() => {
async function fetchNotifications() {
const response = await getNotifications(page - 1, pageSize);
setNotifications(response.data?.data?.content || []);
setTotalData(response.data?.data?.totalElements || 0);
setTotalPage(response.data?.data?.totalPages);
}
fetchNotifications();
}, [page, pageSize]);
const handleNotificationClick = (redirectUrl: string) => {
router.push(redirectUrl);
};
const handlePageChange = (newPage: number) => {
setPage(newPage);
};
return (
<div className="mx-3">
<div className="space-y-3">
{notifications.length > 0 ? (
notifications.map((notification) => (
<div
key={notification.id}
className="flex items-start space-x-4 p-4 border rounded-lg shadow-sm bg-white cursor-pointer"
onClick={() => handleNotificationClick(notification.redirectUrl)}
>
<div className="flex-shrink-0">
<div className="flex-none">
{getNotificationIcon(notification.notificationTypeId)}
</div>
</div>
<div>
<p className="text-sm font-medium">{notification.message}</p>
<p className="text-xs text-gray-500">
{new Date(notification.createdAt).toLocaleString()}
</p>
</div>
</div>
))
) : (
<p className="text-gray-500 text-sm">Tidak ada notifikasi.</p>
)}
</div>
{/* Pagination */}
<div className="flex justify-end items-center mt-6 gap-3 mb-3">
<Button
onClick={() => handlePageChange(page - 1)}
disabled={page === 1}
variant="outline"
className="rounded-full"
size="sm"
>
<ChevronLeft size={20} />
</Button>
<p className="text-sm">
Page {page} of {totalPage}
</p>
<Button
onClick={() => handlePageChange(page + 1)}
disabled={page === totalPage}
variant="outline"
className="rounded-full"
size="sm"
>
<ChevronRight size={20} />
</Button>
</div>
</div>
);
};
export default NotificationsList;

View File

@ -0,0 +1,9 @@
export const metadata = {
title: "Notifications",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -0,0 +1,33 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Link } from "@/components/navigation";
import BlogTable from "../contributor/blog/components/blog-table";
import NotificationsTable from "./components/notifications-table";
const NotificationsPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<Card>
<CardHeader className="border-b border-solid border-default-200 mb-6">
<CardTitle>
<div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900">
Table List Notifications
</div>
</div>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<NotificationsTable />
</CardContent>
</Card>
</div>
</div>
);
};
export default NotificationsPage;

View File

@ -0,0 +1,193 @@
"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { control } from "leaflet";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import { getBlog, postBlog } from "@/service/blog/blog";
import { id } from "date-fns/locale";
import router from "next/router";
import { getInfoProfile, saveUser, setupPassword } from "@/service/auth";
import withReactContent from "sweetalert2-react-content";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Link } from "@/components/navigation";
const profileSchema = z.object({
password: z.string().min(1, { message: "Judul diperlukan" }),
retypePassword: z.string().min(1, { message: "Judul diperlukan" }),
});
type Detail = {
id: number;
userId: any;
password: string;
retypePassword: string;
};
const ChangePassword: React.FC = () => {
const MySwal = withReactContent(Swal);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
type ProfileSchema = z.infer<typeof profileSchema>;
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ProfileSchema>({
resolver: zodResolver(profileSchema),
});
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
const save = async (data: ProfileSchema) => {
const requestData = {
...data,
// userId: detail?.userKeycloakId,
password: data.password,
retypePassword: detail?.retypePassword,
};
const response = await setupPassword(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/auth");
});
};
const onSubmit = (data: ProfileSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
return (
<div className="px-4 lg:px-14 py-8">
<div className="text-center mb-8">
<div className="w-20 h-20 mx-auto rounded-full bg-red-700 flex items-center justify-center">
<span className="text-white text-3xl font-bold">👤</span>
</div>
<h1 className="text-2xl font-bold mt-4">Ubah Profile</h1>
</div>
<div className="flex justify-center gap-4 mb-8">
<Link href={"/profile"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
User Profile
</button>
</Link>
<Link href={"/profile/change-profile"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
Ubah Foto
</button>
</Link>
<Link href={"/profile/change-password"}>
<button className="bg-red-700 text-white px-4 py-2 rounded">
Ubah Password
</button>
</Link>
</div>
<div className="max-w-3xl mx-auto bg-white shadow-sm p-4 rounded">
<p className="mb-6 text-gray-600">
Silahkan ubah data pribadi Anda pada form berikut.
</p>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<div className="mb-4">
<Label>
Password<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="password"
render={({ field }) => (
<Input
size="md"
type="password"
defaultValue={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.password?.message && (
<p className="text-red-400 text-sm">
{errors.password.message}
</p>
)}
</div>
<div className="mb-4">
<Label>
Konfirmasi Password<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="retypePassword"
render={({ field }) => (
<Input
size="md"
type="password"
defaultValue={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.retypePassword?.message && (
<p className="text-red-400 text-sm">
{errors.retypePassword.message}
</p>
)}
</div>
<div className="text-right">
<div className="mt-4">
<button
type="submit"
className="bg-red-700 text-white px-6 py-2 rounded hover:bg-red-800 focus:outline-none focus:ring focus:ring-red-300"
>
Simpan
</button>
</div>
</div>
</div>
</form>
</div>
</div>
);
};
export default ChangePassword;

View File

@ -0,0 +1,96 @@
"use client";
import { Link } from "@/components/navigation";
import React, { useState } from "react";
const ChangeProfile: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setSelectedImage(e.target.files[0]);
}
};
const handleSave = () => {
if (selectedImage) {
console.log("Foto berhasil diganti:", selectedImage.name);
alert("Foto berhasil diganti.");
} else {
alert("Silakan pilih foto terlebih dahulu.");
}
};
const handleDelete = () => {
setSelectedImage(null);
alert("Foto berhasil dihapus.");
};
return (
<div className="px-4 lg:px-14 py-8">
<div className="text-center mb-8">
<div className="w-20 h-20 mx-auto rounded-full bg-red-700 flex items-center justify-center">
<span className="text-white text-3xl font-bold">👤</span>
</div>
<h1 className="text-2xl font-bold mt-4">Ubah Photo</h1>
</div>
<div className="flex justify-center gap-4 mb-8">
<Link href={"/profile"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
User Profile
</button>
</Link>
<Link href={"/profile/change-profile"}>
<button className="bg-red-700 text-white px-4 py-2 rounded">
Ubah Foto
</button>
</Link>
<Link href={"/profile/change-password"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
Ubah Password
</button>
</Link>
</div>
<div className="w-80 mx-auto bg-gray-900 text-white shadow-md p-8 rounded">
<div className="flex justify-center mb-6">
<div className="w-40 h-40 rounded-full border border-gray-300 flex items-center justify-center">
{selectedImage ? (
<img
src={URL.createObjectURL(selectedImage)}
alt="Preview"
className="w-full h-full rounded-full object-cover"
/>
) : (
<span className="text-gray-500">No Image</span>
)}
</div>
</div>
<div className="flex justify-center gap-4">
<input
type="file"
id="upload"
accept="image/*"
className="hidden"
onChange={handleImageChange}
/>
<label
htmlFor="upload"
className="bg-blue-600 text-white px-4 py-2 rounded cursor-pointer"
>
Ganti Foto
</label>
<button
onClick={handleDelete}
className="border border-red-700 text-red-700 px-4 py-2 rounded"
>
Hapus Foto
</button>
</div>
</div>
</div>
);
};
export default ChangeProfile;

View File

@ -0,0 +1,22 @@
import LayoutProvider from "@/providers/layout.provider";
import LayoutContentProvider from "@/providers/content.provider";
import DashCodeSidebar from "@/components/partials/sidebar";
import DashCodeFooter from "@/components/partials/footer";
import ThemeCustomize from "@/components/partials/customizer";
import DashCodeHeader from "@/components/partials/header";
import { redirect } from "@/components/navigation";
import Footer from "@/components/landing-page/footer";
import Navbar from "@/components/landing-page/navbar";
const layout = async ({ children }: { children: React.ReactNode }) => {
return (
<>
<Navbar />
{children}
<Footer />
</>
);
};
export default layout;

View File

@ -0,0 +1,301 @@
"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { control } from "leaflet";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import { getBlog, postBlog } from "@/service/blog/blog";
import { id } from "date-fns/locale";
import router from "next/router";
import { getInfoProfile, saveUser } from "@/service/auth";
import withReactContent from "sweetalert2-react-content";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Link } from "@/components/navigation";
const profileSchema = z.object({
username: z.string().min(1, { message: "Judul diperlukan" }),
fullname: z.string().min(1, { message: "Judul diperlukan" }),
memberIdentity: z.string().min(1, { message: "Judul diperlukan" }),
email: z.string().min(1, { message: "Judul diperlukan" }),
address: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
phoneNumber: z.string().min(1, { message: "Kategori diperlukan" }),
});
type Detail = {
id: number;
userId: any;
firstName: string;
username: string;
fullname: string;
memberIdentity: any;
email: string;
address: string;
phoneNumber: any;
message: string;
};
const UbahProfile: React.FC = () => {
const MySwal = withReactContent(Swal);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
type ProfileSchema = z.infer<typeof profileSchema>;
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ProfileSchema>({
resolver: zodResolver(profileSchema),
});
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
const save = async (data: ProfileSchema) => {
const requestData = {
...data,
// userId: detail?.userKeycloakId,
firstName: detail?.fullname,
username: detail?.username,
memberIdentity: detail?.memberIdentity,
email: detail?.email,
address: detail?.address,
phoneNumber: detail?.phoneNumber,
// message: data.title,
};
const response = await saveUser(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/auth");
});
};
const onSubmit = (data: ProfileSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
return (
<div className="px-4 lg:px-14 py-8">
<div className="text-center mb-8">
<div className="w-20 h-20 mx-auto rounded-full bg-red-700 flex items-center justify-center">
<span className="text-white text-3xl font-bold">👤</span>
</div>
<h1 className="text-2xl font-bold mt-4">Ubah Profile</h1>
</div>
<div className="flex justify-center gap-4 mb-8">
<Link href={"/profile"}>
<button className="bg-red-700 text-white px-4 py-2 rounded">
User Profile
</button>
</Link>
<Link href={"/profile/change-profile"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
Ubah Foto
</button>
</Link>
<Link href={"/profile/change-password"}>
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
Ubah Password
</button>
</Link>
</div>
<div className="max-w-3xl mx-auto bg-white shadow-sm p-4 rounded">
<p className="mb-6 text-gray-600">
Silahkan ubah data pribadi Anda pada form berikut.
</p>
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
<div>
<div className="mb-4">
<Label>
Username<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="username"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.username}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.username?.message && (
<p className="text-red-400 text-sm">
{errors.username.message}
</p>
)}
</div>
<div className="mb-4">
<Label>
Nama Lengkap<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="fullname"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.fullname}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.fullname?.message && (
<p className="text-red-400 text-sm">
{errors.fullname.message}
</p>
)}
</div>
<div className="mb-4">
<Label>
Nomor Identitas<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="memberIdentity"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.memberIdentity}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.memberIdentity?.message && (
<p className="text-red-400 text-sm">
{errors.memberIdentity.message}
</p>
)}
</div>
<div className="mb-4">
<Label>
Email<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="email"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.email}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.email?.message && (
<p className="text-red-400 text-sm">{errors.email.message}</p>
)}
</div>
<div className="mb-4">
<Label>
Nomor HP<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="phoneNumber"
render={({ field }) => (
<Input
size="md"
type="number"
defaultValue={detail?.phoneNumber}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.phoneNumber?.message && (
<p className="text-red-400 text-sm">
{errors.phoneNumber.message}
</p>
)}
</div>
<div className="mb-4">
<Label>
Alamat<span className="text-red-500">*</span>
</Label>
<Controller
control={control}
name="address"
render={({ field }) => (
<Textarea
defaultValue={detail?.address}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.address?.message && (
<p className="text-red-400 text-sm">
{errors.address.message}
</p>
)}
</div>
<div className="text-right">
<div className="mt-4">
<button
type="submit"
className="bg-red-700 text-white px-6 py-2 rounded hover:bg-red-800 focus:outline-none focus:ring focus:ring-red-300"
>
Simpan
</button>
</div>
</div>
</div>
) : (
""
)}
</form>
</div>
</div>
);
};
export default UbahProfile;

View File

@ -0,0 +1,814 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import { createTask } from "@/config/api";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
uploadThumbnail,
} from "@/service/content/content";
import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { loading } from "@/lib/swal";
import {
generateDataArticle,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/content/ai";
import { title } from "process";
import style from "styled-jsx/style";
import { getCookiesDecrypt } from "@/lib/utils";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
mainKeyword: z.string().min(1, { message: "Judul diperlukan" }),
seo: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
export default function FormImageAI() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
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 [selectedTitle, setSelectedTitle] = useState("");
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedContent, setSelectedContent] = useState("single-article");
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 [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "1";
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ImageSchema>({
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 = (articleId: string) => {
// setEditingArticleId(articleId);
// setSelectedArticleId(articleId);
// };
const handleArticleIdClick = async (id: string) => {
// Fetch article details by ID
const res = await getDetailArticle(id);
const articleData = res?.data?.data;
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
// Update state with new article data
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
};
const handleEditClick = () => {
if (editingArticleId) {
console.log("Editing article:", editingArticleId);
// Handle the edit action here
}
};
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
useEffect(() => {
async function initState() {
getCategories();
// setVideoActive(fileTypeId == '2');
// getRoles();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category.data.data.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
const save = async (data: ImageSchema) => {
const requestData = {
...data,
title: data.title,
description: data.description,
htmlDescription: data.description,
fileTypeId,
categoryId: selectedCategory,
subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: "6",
creatorName: data.creatorName,
tags: "siap",
isYoutube: false,
isInternationalMedia: false,
};
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
if (response.error) {
MySwal.fire("Error", response.message, "error");
return;
}
const imageId = response.data.data.id;
if (imageId && thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail);
const responseThumbnail = await uploadThumbnail(imageId, formMedia);
if (responseThumbnail.error) {
MySwal.fire("Error", responseThumbnail.message, "error");
return;
}
}
MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
});
};
const onSubmit = (data: ImageSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file);
console.log("Selected Thumbnail:", file);
}
if (file) {
setPreview(URL.createObjectURL(file));
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3 w-4/12">
<Label>Select Content</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="single-article">
single Article
</SelectItem>
<SelectItem value="bulk-article">Bulk Article</SelectItem>
<SelectItem value="speech-text">Speech to Text</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => {
console.log("Selected Category ID:", id);
setSelectedCategory(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</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="py-3">
<div className="flex flex-row justify-between items-center">
<Label>Deskripsi</Label>
{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>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={articleBody || value} // Ensure the JoditEditor receives the correct article body
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
<div className="py-3">
<Label>Attachment</Label>
<Input
id="fileInput"
type="file"
onChange={handleImageChange}
/>
<div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label>
<div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
{preview && (
<div className="mt-3 px-3">
<img
src={preview}
alt="Thumbnail Preview"
className="w-32 h-auto rounded"
/>
</div>
)}
</div>
</div>
{/* Submit Button */}
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[500px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Kreator</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3">
<Label>Tags</Label>
{/* <Controller
control={control}
name="tags"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
onKeyDown={handleKeyDown}
/>
)}
/> */}
<div className="text-sm text-red-500">
{tags.length === 0 && "Please add at least one tag."}
</div>
<div className="flex flex-wrap gap-2 border border-gray-300 mt-2 rounded-md p-2 items-center">
{tags.map((tag, index) => (
<div
key={index}
className="flex items-center gap-1 bg-blue-100 text-blue-800 rounded-full px-3 py-1 text-sm font-medium"
>
<span>{tag}</span>
<button
className="text-blue-600 hover:text-blue-800 focus:outline-none"
onClick={() => handleRemoveTag(index)}
>
×
</button>
</div>
))}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
<Checkbox id="all" />
<Label htmlFor="all">SEMUA</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="umum" />
<Label htmlFor="umum">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="journalist" />
<Label htmlFor="journalist">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="polri" />
<Label htmlFor="polri">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="ksp" />
<Label htmlFor="ksp">KSP</Label>
</div>
</div>
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
Cancel
</Button>
</div>
</div>
</div>
</div>
</form>
);
}

View File

@ -60,6 +60,7 @@ export default function FormUpdatePressConference() {
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const [location, setLocation] = useState("");
const {
control,
@ -89,6 +90,7 @@ export default function FormUpdatePressConference() {
if (details) {
setStartTime(details.startTime);
setEndTime(details.endTime);
setLocation(details.address);
}
}
}
@ -103,6 +105,12 @@ export default function FormUpdatePressConference() {
setEndTime(e.target.value);
};
const handleLocationChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setLocation(value);
setValue("location", value);
};
const save = async (data: TaskSchema) => {
const requestData: {
id?: number;
@ -123,19 +131,18 @@ export default function FormUpdatePressConference() {
address: data.location,
speakerTitle: data.level,
speakerName: data.name,
startTime, // Start time from state
endTime, // End time from state
addressLat: "0.0", // Replace with actual latitude
addressLong: "0.0", // Replace with actual longitude
startTime,
endTime,
addressLat: "0.0",
addressLong: "0.0",
startDate: date?.from ? format(date.from, "yyyy-MM-dd") : null,
endDate: date?.to ? format(date.to, "yyyy-MM-dd") : null,
isYoutube: isLiveStreamingEnabled,
scheduleTypeId: 1,
};
// Add id property if it exists
if (id) {
requestData.id = parseInt(id, 10); // Ensure id is a number
requestData.id = parseInt(id, 10);
}
console.log("Form Data Submitted:", requestData);
@ -309,10 +316,12 @@ export default function FormUpdatePressConference() {
</div>
</div>
<div>
{/* Kirim setValue ke MapHome */}
<MapHome
draggable
setLocation={(location) => setValue("location", location)}
setLocation={(location) => {
setLocation(location);
setValue("location", location);
}}
/>
</div>
<div>
@ -322,8 +331,8 @@ export default function FormUpdatePressConference() {
render={({ field }) => (
<Textarea
rows={3}
defaultValue={detail?.address}
onChange={field.onChange}
value={location}
onChange={handleLocationChange}
placeholder="Masukan lokasi"
/>
)}

View File

@ -6,11 +6,42 @@ import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi";
import { useParams, usePathname, useRouter } from "next/navigation";
import { generateLocalizedPath } from "@/utils/globals";
import { Link } from "@/i18n/routing";
import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
import { getCookiesDecrypt } from "@/lib/utils";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Icon } from "../ui/icon";
import { getCookiesDecrypt } from "@/lib/utils";
import Cookies from "js-cookie";
import { getInfoProfile } from "@/service/auth";
type Detail = {
id: number;
userId: any;
firstName: string;
username: string;
fullname: string;
memberIdentity: any;
email: string;
address: string;
phoneNumber: any;
message: string;
};
const roleName = getCookiesDecrypt("urne");
const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false);
@ -20,6 +51,18 @@ const Navbar = () => {
const locale = params?.locale;
const [language, setLanguage] = useState<"id" | "en">("id");
const [isOpen, setIsOpen] = useState(false);
const fullname = getCookiesDecrypt("ufne");
const levelName = getCookiesDecrypt("ulnae");
const roleId = getCookiesDecrypt("urie");
const [detail, setDetail] = useState<Detail>();
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName);
});
router.push("/");
};
// const profilePicture = Cookies.get("profile_picture");
const fullName = getCookiesDecrypt("ufne");
@ -38,24 +81,58 @@ const Navbar = () => {
// Render
if (!hasMounted) return null;
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
return (
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-20 py-4 gap-3">
{/* Logo */}
<Link href="/" className="flex items-center">
<img src="/assets/mediahub-logo.gif" alt="Media Hub Logo" className="w-fit h-20 md:h-24" />
<img
src="/assets/mediahub-logo.gif"
alt="Media Hub Logo"
className="w-fit h-20 md:h-24"
/>
</Link>
{/* Mobile Menu Toggle */}
<button className="text-black dark:text-white right-0 size-20 h-10 w-10 lg:hidden" onClick={() => setMenuOpen(!menuOpen)}>
<button
className="text-black dark:text-white right-0 size-20 h-10 w-10 lg:hidden"
onClick={() => setMenuOpen(!menuOpen)}
>
{menuOpen ? (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000" d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="#000"
d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z"
/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000" d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="#000"
d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z"
/>
</svg>
)}
</button>
@ -68,7 +145,14 @@ const Navbar = () => {
<NavigationMenuItem>
<NavigationMenuTrigger>
<a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer">
<svg className="mx-2 dark:" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mx-2 dark:"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20 7.5H5C4.6023 7.5004 4.221 7.65856 3.93978 7.93978C3.65856 8.221 3.5004 8.6023 3.5 9V19.5C3.5004 19.8977 3.65856 20.279 3.93978 20.5602C4.221 20.8414 4.6023 20.9996 5 21H20C20.3977 20.9996 20.779 20.8414 21.0602 20.5602C21.3414 20.279 21.4996 19.8977 21.5 19.5V9C21.4996 8.6023 21.3414 8.221 21.0602 7.93978C20.779 7.65856 20.3977 7.5004 20 7.5ZM10.25 17.25V11.25L15.5 14.25L10.25 17.25ZM5 4.5H20V6H5V4.5ZM6.5 1.5H18.5V3H6.5V1.5Z"
fill="currentColor"
@ -78,25 +162,56 @@ const Navbar = () => {
</a>
</NavigationMenuTrigger>
<NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full">
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 ">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/video/filter", String(locale))
)
}
className="flex items-start gap-1.5 p-2 "
>
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
Video
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 ">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/audio/filter", String(locale))
)
}
className="flex place-items-start gap-1.5 p-2 "
>
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiMusic className="mr-2" />
Audio
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/image/filter", String(locale))
)
}
className="flex place-items-start gap-1.5 p-2"
>
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiImage className="mr-2" />
Foto
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath(
"/document/filter",
String(locale)
)
)
}
className="flex place-items-start gap-1.5 p-2"
>
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiFile className="mr-2" />
Teks
@ -108,7 +223,14 @@ const Navbar = () => {
<Link href="/schedule" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
<span>
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mr-2"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.5 4H18.5V3C18.5 2.4 18.1 2 17.5 2C16.9 2 16.5 2.4 16.5 3V4H8.5V3C8.5 2.4 8.1 2 7.5 2C6.9 2 6.5 2.4 6.5 3V4H5.5C3.8 4 2.5 5.3 2.5 7V8H22.5V7C22.5 5.3 21.2 4 19.5 4ZM2.5 19C2.5 20.7 3.8 22 5.5 22H19.5C21.2 22 22.5 20.7 22.5 19V10H2.5V19ZM17.5 12C18.1 12 18.5 12.4 18.5 13C18.5 13.6 18.1 14 17.5 14C16.9 14 16.5 13.6 16.5 13C16.5 12.4 16.9 12 17.5 12ZM17.5 16C18.1 16 18.5 16.4 18.5 17C18.5 17.6 18.1 18 17.5 18C16.9 18 16.5 17.6 16.5 17C16.5 16.4 16.9 16 17.5 16ZM12.5 12C13.1 12 13.5 12.4 13.5 13C13.5 13.6 13.1 14 12.5 14C11.9 14 11.5 13.6 11.5 13C11.5 12.4 11.9 12 12.5 12ZM12.5 16C13.1 16 13.5 16.4 13.5 17C13.5 17.6 13.1 18 12.5 18C11.9 18 11.5 17.6 11.5 17C11.5 16.4 11.9 16 12.5 16ZM7.5 12C8.1 12 8.5 12.4 8.5 13C8.5 13.6 8.1 14 7.5 14C6.9 14 6.5 13.6 6.5 13C6.5 12.4 6.9 12 7.5 12ZM7.5 16C8.1 16 8.5 16.4 8.5 17C8.5 17.6 8.1 18 7.5 18C6.9 18 6.5 17.6 6.5 17C6.5 16.4 6.9 16 7.5 16Z"
fill="currentColor"
@ -123,7 +245,14 @@ const Navbar = () => {
<Link href="/indeks" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
<span>
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mr-2"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
@ -145,22 +274,40 @@ const Navbar = () => {
</Link>
<div className="flex items-center space-x-1 ">
<a href="https://tvradio.polri.go.id/">
<img src="/assets/polriTv.png" className="w-auto lg:max-w-screen-lg h-10 flex-auto " />
<img
src="/assets/polriTv.png"
className="w-auto lg:max-w-screen-lg h-10 flex-auto "
/>
</a>
</div>
<div className="relative inline-block text-left">
{/* Tombol Utama */}
<button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg"
>
<img
src={language === "id" ? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" : "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"}
src={
language === "id"
? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg"
: "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"
}
alt={language === "id" ? "Ind" : "Eng"}
className="w-3 h-3"
/>
<span>{language === "id" ? "Ind" : "Eng"}</span>
<span className="text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 32 32">
<path fill="currentColor" d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 32 32"
>
<path
fill="currentColor"
d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z"
/>
</svg>
</span>
</button>
@ -168,12 +315,30 @@ const Navbar = () => {
{/* Dropdown Menu */}
{isOpen && (
<div className="absolute right-0 mt-2 w-auto bg-slate-200 border rounded-md shadow-lg z-10">
<button onClick={() => handleLanguageChange("id")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "id" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" alt="Indonesia" className="w-5 h-5" />
<button
onClick={() => handleLanguageChange("id")}
className={`flex items-center space-x-2 w-full px-4 py-2 ${
language === "id" ? "font-medium" : ""
}`}
>
<img
src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg"
alt="Indonesia"
className="w-5 h-5"
/>
<span>Ind</span>
</button>
<button onClick={() => handleLanguageChange("en")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "en" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg" alt="English" className="w-5 h-5" />
<button
onClick={() => handleLanguageChange("en")}
className={`flex items-center space-x-2 w-full px-4 py-2 ${
language === "en" ? "font-medium" : ""
}`}
>
<img
src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"
alt="English"
className="w-5 h-5"
/>
<span>Eng</span>
</button>
</div>
@ -181,9 +346,18 @@ const Navbar = () => {
</div>
<ThemeSwitcher />
<div className="relative text-gray-600 dark:text-white">
<input type="text" placeholder="Pencarian" className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white" />
<input
type="text"
placeholder="Pencarian"
className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white"
/>
<span className="absolute left-4 top-1/2 transform -translate-y-1/2">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24">
<svg
xmlns="http://www.w3.org/2000/svg"
width="13"
height="13"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fill-rule="evenodd"
@ -237,6 +411,173 @@ const Navbar = () => {
</>
)}
</div>
{roleId === "5" ||
roleId === "6" ||
roleId === "7" ||
roleId === "8" ? (
<DropdownMenu>
<DropdownMenuTrigger asChild className="cursor-pointer">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"https://netidhub.com/assets/img/user-avatar.png"}
alt={"Image"}
width={36}
height={36}
className="rounded-full"
/>
<div>
<div className="text-sm font-medium capitalize lg:block hidden">
{detail?.fullname}
</div>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
<DropdownMenuGroup>
{[
{
name: "Profile & Settings",
icon: "heroicons:user",
href: "/profile",
},
{
name: "Kelola Konten",
icon: "heroicons:megaphone",
href: "/dashboard",
},
].map((item, index) => (
<Link
href={item.href}
key={`info-menu-${index}`}
className="cursor-pointer"
>
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize px-3 py-1.5 cursor-pointer">
<Icon icon={item.icon} className="w-4 h-4" />
{item.name}
</DropdownMenuItem>
</Link>
))}
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
<div>
<Link href={"/"}>
<button
type="submit"
className="w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out
</button>
</Link>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : roleId === "2" ||
roleId === "3" ||
roleId === "4" ||
roleId === "9" ||
roleId === "10" ||
roleId === "11" ||
roleId === "12" ||
roleId === "13" ? (
// Dropdown menu for roleId === 3
<DropdownMenu>
<DropdownMenuTrigger asChild className="cursor-pointer">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"https://netidhub.com/assets/img/user-avatar.png"}
alt={"Image"}
width={36}
height={36}
className="rounded-full"
/>
<div>
<div className="text-sm font-medium capitalize lg:block hidden">
{detail?.fullname}
</div>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
<DropdownMenuGroup>
{[
{
name: "Profile & Settings",
icon: "heroicons:user",
href: "/profile",
},
{
name: "Dashboard",
icon: "heroicons:megaphone",
href: "/dashboard",
},
].map((item, index) => (
<Link
href={item.href}
key={`info-menu-${index}`}
className="cursor-pointer"
>
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize px-3 py-1.5 cursor-pointer">
<Icon icon={item.icon} className="w-4 h-4" />
{item.name}
</DropdownMenuItem>
</Link>
))}
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
<div>
<Link href={"/"}>
<button
type="submit"
className="w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out
</button>
</Link>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
// Masuk and Daftar buttons for roleId === null
<div className="flex justify-center items-center mx-3 gap-5">
<Link
href="/auth"
className="w-full lg:w-fit px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
>
Masuk
</Link>
<Link
href="#"
className="w-full lg:w-fit px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] text-center hover:text-white"
>
Daftar
</Link>
</div>
)}
</div>
</div>
@ -248,7 +589,14 @@ const Navbar = () => {
<NavigationMenuItem>
<NavigationMenuTrigger>
<a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer">
<svg className="mx-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mx-2"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20 7.5H5C4.6023 7.5004 4.221 7.65856 3.93978 7.93978C3.65856 8.221 3.5004 8.6023 3.5 9V19.5C3.5004 19.8977 3.65856 20.279 3.93978 20.5602C4.221 20.8414 4.6023 20.9996 5 21H20C20.3977 20.9996 20.779 20.8414 21.0602 20.5602C21.3414 20.279 21.4996 19.8977 21.5 19.5V9C21.4996 8.6023 21.3414 8.221 21.0602 7.93978C20.779 7.65856 20.3977 7.5004 20 7.5ZM10.25 17.25V11.25L15.5 14.25L10.25 17.25ZM5 4.5H20V6H5V4.5ZM6.5 1.5H18.5V3H6.5V1.5Z"
fill="currentColor"
@ -258,25 +606,56 @@ const Navbar = () => {
</a>
</NavigationMenuTrigger>
<NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full">
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 hover:bg-white">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/video/filter", String(locale))
)
}
className="flex items-start gap-1.5 p-2 hover:bg-white"
>
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
Video
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/audio/filter", String(locale))
)
}
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
>
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiMusic className="mr-2" />
Audio
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath("/image/filter", String(locale))
)
}
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
>
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiImage className="mr-2" />
Foto
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<NavigationMenuLink
onClick={() =>
router.push(
generateLocalizedPath(
"/document/filter",
String(locale)
)
)
}
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
>
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiFile className="mr-2" />
Teks
@ -288,7 +667,14 @@ const Navbar = () => {
<Link href="/schedule" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
<span>
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mr-2"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.5 4H18.5V3C18.5 2.4 18.1 2 17.5 2C16.9 2 16.5 2.4 16.5 3V4H8.5V3C8.5 2.4 8.1 2 7.5 2C6.9 2 6.5 2.4 6.5 3V4H5.5C3.8 4 2.5 5.3 2.5 7V8H22.5V7C22.5 5.3 21.2 4 19.5 4ZM2.5 19C2.5 20.7 3.8 22 5.5 22H19.5C21.2 22 22.5 20.7 22.5 19V10H2.5V19ZM17.5 12C18.1 12 18.5 12.4 18.5 13C18.5 13.6 18.1 14 17.5 14C16.9 14 16.5 13.6 16.5 13C16.5 12.4 16.9 12 17.5 12ZM17.5 16C18.1 16 18.5 16.4 18.5 17C18.5 17.6 18.1 18 17.5 18C16.9 18 16.5 17.6 16.5 17C16.5 16.4 16.9 16 17.5 16ZM12.5 12C13.1 12 13.5 12.4 13.5 13C13.5 13.6 13.1 14 12.5 14C11.9 14 11.5 13.6 11.5 13C11.5 12.4 11.9 12 12.5 12ZM12.5 16C13.1 16 13.5 16.4 13.5 17C13.5 17.6 13.1 18 12.5 18C11.9 18 11.5 17.6 11.5 17C11.5 16.4 11.9 16 12.5 16ZM7.5 12C8.1 12 8.5 12.4 8.5 13C8.5 13.6 8.1 14 7.5 14C6.9 14 6.5 13.6 6.5 13C6.5 12.4 6.9 12 7.5 12ZM7.5 16C8.1 16 8.5 16.4 8.5 17C8.5 17.6 8.1 18 7.5 18C6.9 18 6.5 17.6 6.5 17C6.5 16.4 6.9 16 7.5 16Z"
fill="currentColor"
@ -303,7 +689,14 @@ const Navbar = () => {
<Link href="/indeks" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
<span>
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className="mr-2"
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
@ -325,21 +718,39 @@ const Navbar = () => {
</div>
<div className="flex items-center space-x-1 mx-3 text-yellow-600 font-medium">
<a href="https://tvradio.polri.go.id/">
<img src="/assets/polriTv.png" className="w-21 h-11 flex items-center" />
<img
src="/assets/polriTv.png"
className="w-21 h-11 flex items-center"
/>
</a>
</div>
<div className="relative inline-block mx-3 text-left">
{/* Tombol Utama Bahasa */}
<button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg"
>
<img
src={language === "id" ? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" : "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"}
src={
language === "id"
? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg"
: "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"
}
alt={language === "id" ? "Ind" : "Eng"}
className="w-3 h-3"
/>
<span>{language === "id" ? "Ind" : "Eng"}</span>
<span className="text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 32 32">
<path fill="currentColor" d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 32 32"
>
<path
fill="currentColor"
d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z"
/>
</svg>
</span>
</button>
@ -347,12 +758,30 @@ const Navbar = () => {
{/* Dropdown Menu */}
{isOpen && (
<div className="absolute right-0 mt-2 w-auto bg-slate-200 border rounded-md shadow-lg z-10">
<button onClick={() => handleLanguageChange("id")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "id" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" alt="Indonesia" className="w-5 h-5" />
<button
onClick={() => handleLanguageChange("id")}
className={`flex items-center space-x-2 w-full px-4 py-2 ${
language === "id" ? "font-medium" : ""
}`}
>
<img
src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg"
alt="Indonesia"
className="w-5 h-5"
/>
<span>Ind</span>
</button>
<button onClick={() => handleLanguageChange("en")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "en" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg" alt="English" className="w-5 h-5" />
<button
onClick={() => handleLanguageChange("en")}
className={`flex items-center space-x-2 w-full px-4 py-2 ${
language === "en" ? "font-medium" : ""
}`}
>
<img
src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"
alt="English"
className="w-5 h-5"
/>
<span>Eng</span>
</button>
</div>
@ -361,7 +790,11 @@ const Navbar = () => {
</div>
<div className=" py-1 flex items-center mx-3">
<input type="text" placeholder="Pencarian" className="border rounded-full w-full text-sm text-center text-gray-600" />
<input
type="text"
placeholder="Pencarian"
className="border rounded-full w-full text-sm text-center text-gray-600"
/>
</div>
<div className="flex justify-center items-center mx-3 gap-5">
{fullName ? (
@ -376,6 +809,18 @@ const Navbar = () => {
</Link>{" "}
</>
)}
<Link
href="/auth"
className="w-full lg:w-fit px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
>
Masuk
</Link>
<Link
href="#"
className="w-full lg:w-fit px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] text-center hover:text-white"
>
Daftar
</Link>
</div>
</div>
)}

View File

@ -1,4 +1,4 @@
"use client"
"use client";
import React from "react";
import HeaderContent from "./header-content";
@ -26,8 +26,8 @@ const DashCodeHeader = () => {
<div className="nav-tools flex items-center md:gap-4 gap-3">
<LocalSwitcher />
<ThemeSwitcher />
<Cart />
<Messages />
{/* <Cart />
<Messages /> */}
<Notifications />
<ProfileInfo />
<SheetMenu />

View File

@ -1,4 +1,3 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@ -10,21 +9,103 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Link } from '@/i18n/routing';
import { Link } from "@/i18n/routing";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import { notifications, type Notification } from "./data";
import shortImage from "@/public/images/all-img/short-image-2.png";
import { Icon } from "@/components/ui/icon";
import { useEffect, useState } from "react";
import { getNotifications } from "@/service/notifications/notifications";
import { format, formatDate } from "date-fns";
import {
CalendarCheck,
CheckCheck,
CircleAlert,
Clock7,
MessageCircle,
SquareCheck,
UploadIcon,
} from "lucide-react";
import { useRouter } from "next/navigation";
export type Notification = {
id: number;
notificationTypeId: number;
message: string;
createdAt: string;
isActive: boolean;
isPublic: boolean;
isRead: boolean;
redirectUrl: string;
userGroupIdDst: string | null;
userIdDst: string | null;
userLevelIdDst: string;
userLevelNumberDst: string | null;
userRoleIdDst: string;
};
const getNotificationIcon = (notificationTypeId: number) => {
const router = useRouter();
switch (notificationTypeId) {
case 2:
return <MessageCircle className="h-8 w-8 text-success" />;
case 3:
return <UploadIcon className="h-5 w-5 text-warning" />;
case 4:
return <CheckCheck className="h-5 w-5 text-primary" />;
case 5:
return <SquareCheck className="h-5 w-5 text-warning" />;
case 6:
return <CalendarCheck className="h-5 w-5 text-danger" />;
case 7:
return <CircleAlert className="h-5 w-5 text-danger" />;
case 8:
return <Clock7 className="h-5 w-5 text-danger" />;
default:
return <SquareCheck className="h-5 w-5 text-danger" />;
}
};
const Notifications = () => {
const router = useRouter();
const [notifications, setNotifications] = useState<Notification[]>([]);
const [notificationTotal, setNotificationTotal] = useState(0);
useEffect(() => {
async function initState() {
const response = await getNotifications();
setNotifications(response.data?.data?.content);
setNotificationTotal(response.data?.data?.totalElements);
console.log("notif", response.data?.data?.content);
}
initState();
}, []);
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return format(date, "dd/MM/yyyy HH:mm");
};
const handleNotificationClick = (redirectUrl: string) => {
router.push(redirectUrl);
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button" className="relative hidden focus:ring-none focus:outline-none md:h-8 md:w-8 md:bg-secondary text-secondary-foreground rounded-full md:flex flex-col items-center justify-center">
<Icon icon="heroicons-outline:bell" className="animate-tada h-5 w-5" />
<Badge className=" w-4 h-4 p-0 text-[8px] rounded-full font-semibold items-center justify-center absolute left-[calc(100%-12px)] bottom-[calc(100%-10px)]" color="destructive">
2
<button
type="button"
className="relative hidden focus:ring-none focus:outline-none md:h-8 md:w-8 md:bg-secondary text-secondary-foreground rounded-full md:flex flex-col items-center justify-center"
>
<Icon
icon="heroicons-outline:bell"
className="animate-tada h-5 w-5"
/>
<Badge
className=" w-4 h-4 p-0 text-[8px] rounded-full font-semibold items-center justify-center absolute left-[calc(100%-12px)] bottom-[calc(100%-10px)]"
color="destructive"
>
{notificationTotal > 99 ? "99+" : notificationTotal}
</Badge>
</button>
</DropdownMenuTrigger>
@ -32,13 +113,11 @@ const Notifications = () => {
align="end"
className=" z-[999] mx-4 lg:w-[320px] p-0"
>
<DropdownMenuLabel
>
<DropdownMenuLabel>
<div className="flex justify-between px-4 py-3 border-b border-default-100 ">
<div className="text-sm text-default-800 font-medium ">
Notifications
you have {notificationTotal > 99 ? "99+" : notificationTotal}{" "}
notifications
</div>
<div className="text-default-800 text-xs md:text-right">
<Link href="/notifications" className="underline">
@ -53,35 +132,34 @@ const Notifications = () => {
<DropdownMenuItem
key={`inbox-${index}`}
className="flex gap-9 py-2 px-4 cursor-pointer group "
onClick={() => handleNotificationClick(item?.redirectUrl)}
>
<div className="flex items-start gap-2 flex-1">
<div className="flex items-start gap-2 flex-1 ">
<div className="flex-none">
<Avatar className="h-8 w-8 ">
<AvatarImage src={item.avatar} />
<AvatarFallback> {item.title.charAt(0)}</AvatarFallback>
</Avatar>
{getNotificationIcon(item.notificationTypeId)}
</div>
<div className="flex-1 flex flex-col gap-0.5">
<div className="text-sm text-default-600 dark:group-hover:text-default-800 font-normal truncate">
{item.title}
<div className="text-sm text-default-600 dark:group-hover:text-default-800 font-normal truncate whitespace-normal ">
{item?.message}
</div>
<div className="text-xs text-default-600 dark:group-hover:text-default-700 font-light line-clamp-1 ">
{item.desc}
</div>
<div className=" text-default-400 dark:group-hover:text-default-500 text-xs"> {item.date}</div>
{/* <div className="text-xs text-default-600 dark:group-hover:text-default-700 font-light line-clamp-1 ">
{item?.desc}
</div> */}
<div className=" text-default-400 dark:group-hover:text-default-500 text-xs">
{" "}
{formatDate(item?.createdAt)}
</div>
</div>
{item.unreadmessage && (
</div>
{/* {item?.unreadmessage && (
<div className="flex-0">
<span className="h-[10px] w-[10px] bg-destructive border border-destructive-foreground dark:border-default-400 rounded-full inline-block" />
</div>
)}
)} */}
</DropdownMenuItem>
))}
</ScrollArea>
</div>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -17,38 +17,82 @@ import Image from "next/image";
import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button";
import Cookies from "js-cookie";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useRouter } from "@/components/navigation";
import { getInfoProfile } from "@/service/auth";
type Detail = {
id: number;
userId: any;
firstName: string;
username: string;
fullname: string;
memberIdentity: any;
email: string;
address: string;
phoneNumber: any;
message: string;
};
const ProfileInfo = () => {
const username = Cookies.get("state");
const picture = Cookies.get("profile_picture");
const router = useRouter();
const [detail, setDetail] = useState<Detail>();
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName);
});
router.push("/");
};
useEffect(() => {
console.log("us", username);
}, [username]);
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
return (
<div className="md:block hidden">
<DropdownMenu>
<DropdownMenuTrigger asChild className=" cursor-pointer">
<div className=" flex items-center gap-3 text-default-800 ">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"/images/avatar/icon-avatar-1.png"}
alt={username as string}
src={"https://netidhub.com/assets/img/user-avatar.png"}
alt={"Image"}
width={36}
height={36}
className="rounded-full"
/>
<div className="text-sm font-medium capitalize lg:block hidden ">
{username}
<div>
<div className="text-sm font-medium capitalize lg:block hidden">
{detail?.fullname}
</div>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
<DropdownMenuLabel className="flex gap-2 items-center mb-1 p-3">
{/* <DropdownMenuLabel className="flex gap-2 items-center mb-1 p-3">
<Image
src={"/images/avatar/icon-avatar-1.png"}
alt={username as string}
@ -68,29 +112,29 @@ const ProfileInfo = () => {
{username}
</Link>
</div>
</DropdownMenuLabel>
</DropdownMenuLabel> */}
<DropdownMenuGroup>
{[
{
name: "profile",
name: "profile & Settings",
icon: "heroicons:user",
href: "/user-profile",
},
{
name: "Billing",
icon: "heroicons:megaphone",
href: "/dashboard",
},
{
name: "Settings",
icon: "heroicons:paper-airplane",
href: "/dashboard",
},
{
name: "Keyboard shortcuts",
icon: "heroicons:language",
href: "/dashboard",
href: "/profile",
},
// {
// name: "Billing",
// icon: "heroicons:megaphone",
// href: "/dashboard",
// },
// {
// name: "Settings",
// icon: "heroicons:paper-airplane",
// href: "/dashboard",
// },
// {
// name: "Keyboard shortcuts",
// icon: "heroicons:language",
// href: "/dashboard",
// },
].map((item, index) => (
<Link
href={item.href}
@ -106,7 +150,7 @@ const ProfileInfo = () => {
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<Link href="/dashboard" className="cursor-pointer">
{/* <Link href="/dashboard" className="cursor-pointer">
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize px-3 py-1.5 cursor-pointer">
<Icon icon="heroicons:user-group" className="w-4 h-4" />
team
@ -176,25 +220,20 @@ const ProfileInfo = () => {
))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuSub> */}
</DropdownMenuGroup>
<DropdownMenuSeparator className="mb-0 dark:bg-background" />
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
<div>
<form
// action={async () => {
// "use server";
// await signOut();
// }}
>
<Link href={"/auth"}>
<button
type="submit"
className=" w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out
</button>
</form>
</Link>
</div>
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -155,6 +155,9 @@ importers:
'@types/js-cookie':
specifier: ^3.0.6
version: 3.0.6
'@types/next':
specifier: ^9.0.0
version: 9.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/qs':
specifier: ^6.9.17
version: 6.9.17
@ -249,7 +252,7 @@ importers:
specifier: ^2.30.1
version: 2.30.1
next:
specifier: 14.2.3
specifier: ^14.2.3
version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-intl:
specifier: ^3.19.1
@ -1848,6 +1851,10 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/next@9.0.0':
resolution: {integrity: sha512-gnBXM8rP1mnCgT1uE2z8SnpFTKRWReJlhbZLZkOLq/CH1ifvTNwjIVtXvsywTy1dwVklf+y/MB0Eh6FOa94yrg==}
deprecated: This is a stub types definition. next provides its own type definitions, so you do not need this installed.
'@types/node@20.17.10':
resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==}
@ -6656,6 +6663,18 @@ snapshots:
'@types/ms@0.7.34': {}
'@types/next@9.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
transitivePeerDependencies:
- '@babel/core'
- '@opentelemetry/api'
- '@playwright/test'
- babel-plugin-macros
- react
- react-dom
- sass
'@types/node@20.17.10':
dependencies:
undici-types: 6.19.8

View File

@ -1,4 +1,10 @@
import { getAPI, postAPI, postAPIWithJson } from "../config/api";
import {
getAPI,
getAPIInterceptor,
postAPI,
postAPIInterceptor,
postAPIWithJson,
} from "../config/api";
import { getAPIDummy } from "./http-config/axiosCustom";
import {
@ -16,13 +22,20 @@ export async function getProfile(token: any) {
return getAPI(url, token);
}
// export async function setLogin(data: any) {
// const pathUrl = `signin`;
// const headers = {
// "content-type": "application/json",
// };
// return await httpPost(pathUrl, headers, data);
// }
export async function getInfoProfile() {
const url = "users/info";
return getAPIInterceptor(url);
}
export async function setupPassword(data: any) {
const url = "setup-password";
return postAPIInterceptor(url, data);
}
export async function saveUser(data: any) {
const url = "users/save";
return postAPIInterceptor(url, data);
}
export async function userInfo(token: any) {
const pathUrl = `users/info`;

111
service/content/ai.ts Normal file
View File

@ -0,0 +1,111 @@
import { getAPIInterceptor } from "@/config/api";
import { httpGet, httpPost } from "../http-config/nulisAIApi";
export async function generateDataArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-article", headers, data);
}
export async function getGenerateTitle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-title", headers, data);
}
export async function getGenerateKeywords(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-keywords", headers, data);
}
export async function getDetailArticle(id: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/article/findArticleById/${id}`, headers);
}
export async function getSeoScore(id: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/article/checkSEOScore/${id}`, headers);
}
export async function updateManualArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/update-article", headers, data);
}
export async function getGenerateTopicKeywords(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-topic-keywords", headers, data);
}
export async function saveBulkArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-bulk-article", headers, data);
}
export async function getGenerateRewriter(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/create-rewriter", headers, data);
}
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 generateDataBulkArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-bulk-article", headers, data);
}
export async function listNulisAI(page: any, limit: any) {
const url = `media/nulis-ai/pagination?enablePage=1&page=${page}&size=${limit}`;
return getAPIInterceptor(url);
}
export async function detailMediaNulis(id: any) {
const url = `media/nulis-ai?id=${id}`;
return getAPIInterceptor({ url });
}

View File

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

View File

@ -0,0 +1,47 @@
import axiosNulisAIInstance from "./axiosNulisAIInstance";
export async function httpPost(pathUrl: any, headers: any, data: any) {
const response = await axiosNulisAIInstance
.post(pathUrl, data, { headers })
.catch(function (error: any) {
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 axiosNulisAIInstance
.get(pathUrl, { headers })
.catch(function (error: any) {
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,6 @@
import { getAPIInterceptor } from "@/config/api";
export async function getNotifications(page = 0, pageSize = 10) {
const url = `notification/list?enablePage=1&page=${page}&pageSize=${pageSize}`;
return getAPIInterceptor(url);
}