web-humas-fe/components/form/article/edit-article-form.tsx

1252 lines
39 KiB
TypeScript

"use client";
import { FormEvent, Fragment, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { Input, Textarea } from "@heroui/input";
import dynamic from "next/dynamic";
import JoditEditor from "jodit-react";
import { useDropzone } from "react-dropzone";
import { Button } from "@heroui/button";
import { CloudUploadIcon, TimesIcon } from "@/components/icons";
import Image from "next/image";
import { Switch } from "@heroui/switch";
import {
createArticle,
deleteArticle,
deleteArticleFiles,
getArticleByCategory,
getArticleById,
submitApproval,
updateArticle,
uploadArticleFile,
uploadArticleThumbnail,
} from "@/services/article";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import {
Accordion,
AccordionItem,
Calendar,
Chip,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Popover,
PopoverContent,
PopoverTrigger,
Tab,
Tabs,
useDisclosure,
} from "@heroui/react";
import GenerateSingleArticleForm from "./generate-ai-single-form";
import {
convertDateFormatNoTime,
getUnixTimestamp,
formatMonthString,
getCookiesDecrypt,
htmlToString,
} from "@/utils/global";
import { close, error, loading } from "@/config/swal";
import { useParams, useRouter } from "next/navigation";
import { fromJSON, list } from "postcss";
import GetSeoScore from "./get-seo-score-form";
import Link from "next/link";
import { stringify } from "querystring";
import Cookies from "js-cookie";
import { PdfIcon, WordIcon } from "@/components/icons/globals";
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
interface FileWithPreview extends File {
preview: string;
}
interface CategoryType {
id: number;
label: string;
value: number;
}
const categorySchema = z.object({
id: z.number(),
label: z.string(),
value: z.number(),
});
const createArticleSchema = z.object({
title: z.string().min(2, {
message: "Judul harus diisi",
}),
slug: z.string().min(2, {
message: "Slug harus diisi",
}),
description: z.string().min(2, {
message: "Deskripsi harus diisi",
}),
category: z.array(categorySchema).nonempty({
message: "Kategori harus memiliki setidaknya satu item",
}),
tags: z.array(z.string()).nonempty({
message: "Minimal 1 tag",
}), // Array berisi string
});
interface DiseData {
id: number;
articleBody: string;
title: string;
metaTitle: string;
description: string;
metaDescription: string;
mainKeyword: string;
additionalKeywords: string;
}
const renderPreview = (file: File) => {
if (file.type === "application/pdf") {
return <PdfIcon size={60} />;
} else if (
file.type ===
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
file.type === "application/msword"
) {
return <WordIcon size={60} />;
} else {
return "File";
}
};
export default function EditArticleForm(props: { isDetail: boolean }) {
const { isDetail } = props;
const params = useParams();
const id = params?.id;
const username = getCookiesDecrypt("username");
const userId = getCookiesDecrypt("uie");
const animatedComponents = makeAnimated();
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [documentFiles, setDocumentFiles] = useState<File[]>([]);
const [useAi, setUseAI] = useState(false);
const [listCategory, setListCategory] = useState<CategoryType[]>([]);
const [tag, setTag] = useState("");
const [detailfiles, setDetailFiles] = useState<any>([]);
const [mainImage, setMainImage] = useState(0);
const [thumbnail, setThumbnail] = useState("");
const [diseId, setDiseId] = useState(0);
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null
);
const [thumbnailValidation, setThumbnailValidation] = useState("");
const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [approvalStatus, setApprovalStatus] = useState<number>(2);
const [approvalMessage, setApprovalMessage] = useState("");
const [detailData, setDetailData] = useState<any>();
const [startDateValue, setStartDateValue] = useState<any>(null);
const [timeValue, setTimeValue] = useState("00:00");
const [documentValidation, setDocumentValidation] = useState("");
const [filesValidation, setFileValidation] = useState("");
const [selectedFileType, setSelectedFileType] = useState("image");
const { getRootProps, getInputProps } = useDropzone({
accept:
selectedFileType === "image"
? {
"image/*": [],
}
: {
"application/pdf": [],
"application/msword": [],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
[],
"application/vnd.ms-powerpoint": [],
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
[],
},
onDrop: (acceptedFiles) => {
if (selectedFileType === "image") {
setFiles((prevFiles) => [
...prevFiles,
...acceptedFiles.map((file) => Object.assign(file)),
]);
} else {
setDocumentFiles((prevFiles) => [
...prevFiles,
...acceptedFiles.map((file) => Object.assign(file)),
]);
}
},
multiple: true,
});
const formOptions = {
resolver: zodResolver(createArticleSchema),
defaultValues: { title: "", description: "", category: [], tags: [] },
};
type UserSettingSchema = z.infer<typeof createArticleSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
watch,
setError,
clearErrors,
} = useForm<UserSettingSchema>(formOptions);
useEffect(() => {
initState();
}, [listCategory]);
async function initState() {
loading();
const res = await getArticleById(id);
const data = res.data?.data;
setDetailData(data);
setValue("title", data?.title);
setValue("slug", data?.slug);
setValue("description", data?.htmlDescription);
setValue("tags", data?.tags ? data.tags.split(",") : []);
setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files);
if (
data?.files &&
(data.files[0].file_name.split(".")[1].includes("doc") ||
data.files[0].file_name.split(".")[1].includes("pdf"))
) {
setSelectedFileType("document");
} else {
setSelectedFileType("image");
}
setupInitCategory(data?.categories);
close();
}
const setupInitCategory = (data: any) => {
const temp: CategoryType[] = [];
for (let i = 0; i < data?.length; i++) {
const datas = listCategory.filter((a) => a.id == data[i].id);
if (datas[0]) {
temp.push(datas[0]);
}
}
setValue("category", temp as [CategoryType, ...CategoryType[]]);
};
useEffect(() => {
fetchCategory();
}, []);
const fetchCategory = async () => {
const res = await getArticleByCategory(getUnixTimestamp());
if (res?.data?.data) {
setupCategory(res?.data?.data);
}
};
const setupCategory = (data: any) => {
const temp = [];
for (const element of data) {
temp.push({
id: element.id,
label: element.title,
value: element.id,
});
}
setListCategory(temp);
};
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
MySwal.fire({
title: "Simpan Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(values);
}
});
};
const doPublish = async () => {
MySwal.fire({
title: "Publish Artikel?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Submit",
}).then((result) => {
if (result.isConfirmed) {
publish();
}
});
};
const publish = async () => {
const response = await updateArticle(String(id), {
id: Number(id),
isPublish: true,
title: detailData?.title,
typeId: 1,
slug: detailData?.slug,
categoryIds: getValues("category")
.map((val) => val.id)
.join(","),
tags: getValues("tags").join(","),
description: htmlToString(getValues("description")),
htmlDescription: getValues("description"),
});
if (response?.error) {
error(response.message);
return false;
}
successSubmit("/admin/article");
};
const save = async (values: z.infer<typeof createArticleSchema>) => {
loading();
const formData: any = {
id: Number(id),
title: values.title,
typeId: 1,
slug: values.slug,
categoryIds: values.category.map((val) => val.id).join(","),
tags: values.tags.join(","),
description: htmlToString(values.description),
htmlDescription: values.description,
// createdAt: `${startDateValue} ${timeValue}:00`,
};
if (startDateValue && timeValue) {
formData.createdAt = `${startDateValue} ${timeValue}:00`;
}
const response = await updateArticle(String(id), formData);
if (response?.error) {
error(response.message);
return false;
}
const formFiles = new FormData();
if (files?.length > 0) {
for (const element of files) {
formFiles.append("file", element);
const resFile = await uploadArticleFile(String(id), formFiles);
}
}
if (thumbnailImg?.length > 0) {
const formFiles = new FormData();
formFiles.append("files", thumbnailImg[0]);
const resFile = await uploadArticleThumbnail(String(id), formFiles);
}
close();
successSubmit("/admin/article");
};
function successSubmit(redirect: string) {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
const watchTitle = watch("title");
const generateSlug = (title: string) => {
return title
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/\s+/g, "-");
};
useEffect(() => {
setValue("slug", generateSlug(watchTitle));
}, [watchTitle]);
const renderFilePreview = (file: FileWithPreview) => {
if (file.type.startsWith("image")) {
return (
<img
alt={file.name}
src={URL.createObjectURL(file)}
className="h-[50px]"
/>
);
} else {
return "Not Found";
}
};
const handleRemoveFile = (file: FileWithPreview | File, type: string) => {
if (type === "image") {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
}
if (type === "document") {
const uploadedFiles = documentFiles;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setDocumentFiles([...filtered]);
}
};
const fileList = files.map((file) => (
<div
key={file.name}
className=" flex justify-between border px-3.5 py-3 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div>
<div>
<div className=" text-sm text-card-foreground">{file.name}</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
className=" border-none rounded-full"
variant="bordered"
onPress={() => handleRemoveFile(file, "image")}
>
<TimesIcon />
</Button>
</div>
));
const documentList = documentFiles.map((file, index) => (
<div
key={file.name}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderPreview(file)}</div>
<div>
<div className=" text-sm text-card-foreground">{file.name}</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
className=" border-none rounded-full"
variant="bordered"
onPress={() => handleRemoveFile(file, "document")}
>
<TimesIcon className="text-black" />
</Button>
</div>
));
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus File",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteFile(id);
}
});
};
const deleteFile = async (id: number) => {
loading();
const res = await deleteArticleFiles(id);
if (res?.error) {
error(res.message);
return false;
}
close();
initState();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
}
});
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = event.target.files;
if (selectedFiles) {
setThumbnailImg(Array.from(selectedFiles));
}
};
const approval = async () => {
loading();
const req = {
articleId: Number(id),
message: approvalMessage,
statusId: approvalStatus,
};
const res = await submitApproval(req);
if (res?.error) {
error(res.message);
return false;
}
close();
initState();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
}
});
};
const doApproval = () => {
MySwal.fire({
title: "Submit Data?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Submit",
}).then((result) => {
if (result.isConfirmed) {
approval();
}
});
};
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Artikel",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: any) {
// loading();
const resDelete = await deleteArticle(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
router.push("/admin/article");
}
return (
<form
className="flex flex-col lg:flex-row gap-8 text-black"
onSubmit={handleSubmit(onSubmit)}
>
<div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
{isDetail && <GetSeoScore id={String(diseId)} />}
<p className="text-sm">Judul</p>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
isReadOnly={isDetail}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.title && (
<p className="text-red-400 text-sm mb-3">{errors.title?.message}</p>
)}
<p className="text-sm mt-3">Slug</p>
<Controller
control={control}
name="slug"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.slug && (
<p className="text-red-400 text-sm mb-3">{errors.slug?.message}</p>
)}
{/* <Switch isSelected={useAi} onValueChange={setUseAI} className="mt-3">
<p className="text-sm text-black">Bantuan AI</p>
</Switch> */}
{useAi && (
<GenerateSingleArticleForm
content={(data) => setValue("description", data?.articleBody)}
/>
)}
<p className="text-sm mt-3">Deskripsi</p>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isDetail ? (
<ViewEditor initialData={value} />
) : (
<CustomEditor onChange={onChange} initialData={value} />
)
}
/>
{errors?.description && (
<p className="text-red-400 text-sm mb-3">
{errors.description?.message}
</p>
)}
<p className="text-sm mt-3">File Media</p>
{!isDetail && (
<Tabs
isDisabled
selectedKey={selectedFileType}
onSelectionChange={(e) => {
setSelectedFileType(String(e));
}}
>
<Tab key="image" title="Foto">
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon size={50} className="text-gray-300" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png. Ukuran
maksimal 100mb.)
</div>
</div>
</div>
{files.length ? (
<Fragment>
<div>{fileList}</div>
<div className=" flex justify-between gap-2">
<Button onPress={() => setFiles([])} size="sm">
Hapus Semua
</Button>
</div>
</Fragment>
) : null}
</Fragment>
{filesValidation !== "" && files.length < 1 && (
<p className="text-red-400 text-sm mb-3">Upload File Media</p>
)}
</Tab>
<Tab key="document" title="Dokumen">
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon size={50} className="text-gray-300" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .pdf, atau .docx. Ukuran
maksimal 100mb.)
</div>
</div>
</div>
{documentFiles.length ? (
<Fragment>
<div>{documentList}</div>
<div className=" flex justify-between gap-2">
<Button onPress={() => setFiles([])} size="sm">
Hapus Semua
</Button>
</div>
</Fragment>
) : null}
</Fragment>
{documentValidation !== "" && documentFiles.length < 1 && (
<p className="text-red-400 text-sm mb-3">Upload Document</p>
)}
</Tab>
</Tabs>
)}
{isDetail ? (
detailfiles?.length > 0 ? (
selectedFileType === "document" ? (
detailfiles?.map((file: any, index: number) => (
<Accordion key={file?.id} variant="splitted" className="px-0">
<AccordionItem
key={file?.id}
aria-label={file?.title}
className="p-2"
title={
<p className="text-black dark:text-white font-semibold text-xs md:text-sm">
{`File ${index + 1}`}
</p>
}
>
<div className="bg-[#FFFFFF] rounded-md font-medium text-xs md:text-sm">
<div className="flex justify-between items-center border text-black dark:text-white border-gray-300 p-3 rounded-t-md bg-gray-100 dark:bg-stone-900">
<div className="w-[35%] md:w-[20%]">Nama File</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{file?.file_name}
</div>
</div>
<div className="flex justify-between border items-center border-gray-300 p-3 text-black">
<div className="w-[35%] md:w-[20%]">Ukuran File</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{Math.round(file?.size / 100) / 10 > 1000 ? (
<>
{(Math.round(file?.size / 100) / 10000).toFixed(
1
)}
</>
) : (
<>
{(Math.round(file?.size / 100) / 10).toFixed(1)}
</>
)}
{" kb"}
</div>
</div>
<div className="flex justify-between items-center border rounded-b-md border-gray-300 p-3 text-black">
<div className="w-[35%] md:w-[20%]">
Tanggal Publish
</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{formatMonthString(file?.created_at)}
</div>
</div>
</div>
<Link href={file?.file_url} target="_blank">
<Button
className="w-full bg-[#DD8306] text-sm font-semibold text-white mt-2"
radius="sm"
// onPress={() => doDownload(file?.fileName, file?.title)}
>
File
</Button>
</Link>
</AccordionItem>
</Accordion>
))
) : (
<>
<div className="w-full h-[480px] flex justify-center items-center">
<Image
alt="main"
width={720}
height={480}
src={detailfiles[mainImage]?.file_url}
className={`h-[480px] object-cover w-auto`}
/>
</div>
<div className="flex flex-row gap-2 mt-2">
{detailfiles?.map((file: any, index: number) => (
<a
key={index}
onClick={() => setMainImage(index)}
className="cursor-pointer"
>
<Image
width={480}
height={360}
alt={`image-${index}`}
src={file.file_url}
className="h-[100px] object-cover w-[150px]"
/>
</a>
))}
</div>
</>
)
) : (
<p>Belum Ada File</p>
)
) : (
<div className="flex flex-col">
{detailfiles?.map((file: any, index: number) => (
<div
key={file?.file_name + index}
className=" flex justify-between border px-3.5 py-3 rounded-md"
>
<div className="flex gap-3 items-center">
{selectedFileType === "image" ? (
<div className="file-preview">
<Image
width={480}
height={360}
alt={`image-${index}`}
src={file?.file_url}
className="h-[100px] object-cover w-[150px]"
/>
</div>
) : file.file_name.split(".")[1].includes("pdf") ? (
<PdfIcon size={60} />
) : (
<WordIcon size={60} />
)}
<div>
<div className=" text-sm text-card-foreground">
{file?.file_name}
</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file?.size / 100) / 10 > 1000 ? (
<>{(Math.round(file?.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file?.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
className=" border-none rounded-full"
variant="bordered"
color="danger"
onClick={() => handleDeleteFile(file?.id)}
>
<TimesIcon />
</Button>
</div>
))}
</div>
)}
</div>
<div className="w-full lg:w-[35%] flex flex-col gap-8">
<div className="h-fit bg-white rounded-lg p-8 flex flex-col gap-1">
<p className="text-sm">Thubmnail</p>
{isDetail && thumbnail !== "" ? (
<Image
width={480}
height={360}
src={thumbnail}
className="w-[30%]"
alt="thumbnail"
/>
) : selectedMainImage && files.length >= selectedMainImage ? (
<div className="flex flex-row">
<Image
width={480}
height={360}
src={URL.createObjectURL(files[selectedMainImage - 1])}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
size="sm"
color="danger"
onClick={() => setSelectedMainImage(null)}
>
<TimesIcon />
</Button>
</div>
) : thumbnail !== "" ? (
<div className="flex flex-row">
<Image
width={480}
height={360}
src={thumbnail}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
size="sm"
color="danger"
onPress={() => setThumbnail("")}
>
<TimesIcon />
</Button>
</div>
) : thumbnailImg.length > 0 ? (
<div className="flex flex-row">
<Image
width={480}
height={360}
src={URL.createObjectURL(thumbnailImg[0])}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
size="sm"
color="danger"
onPress={() => setThumbnailImg([])}
>
<TimesIcon />
</Button>
</div>
) : (
<>
<input
id="file-upload"
type="file"
multiple
className="w-fit h-fit"
accept="image/*"
onChange={handleFileChange}
/>
{thumbnailValidation !== "" && (
<p className="text-red-400 text-sm mb-3">Thumbnail harus ada</p>
)}
</>
)}
<p className="text-sm mt-3">Kategori</p>
<Controller
control={control}
name="category"
render={({ field: { onChange, value } }) => (
<ReactSelect
className="basic-single text-black z-40"
classNames={{
control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
}}
classNamePrefix="select"
value={value}
onChange={onChange}
closeMenuOnSelect={false}
components={animatedComponents}
isClearable={true}
isSearchable={true}
isDisabled={isDetail}
isMulti={true}
placeholder="Kategori..."
name="sub-module"
options={listCategory}
/>
)}
/>
{errors?.category && (
<p className="text-red-400 text-sm mb-3">
{errors.category?.message}
</p>
)}
<p className="text-sm">Tags</p>
<Controller
control={control}
name="tags"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="tags"
placeholder=""
label=""
value={tag}
isReadOnly={isDetail}
onValueChange={setTag}
startContent={
<div className="flex flex-wrap gap-1">
{value.map((item, index) => (
<Chip
color="primary"
key={index}
className=""
onClose={() => {
const filteredTags = value.filter(
(tag) => tag !== item
);
if (filteredTags.length === 0) {
setError("tags", {
type: "manual",
message: "Tags tidak boleh kosong",
});
} else {
clearErrors("tags");
setValue(
"tags",
filteredTags as [string, ...string[]]
);
}
}}
>
{item}
</Chip>
))}
</div>
}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (tag.trim() !== "") {
setValue("tags", [...value, tag.trim()]);
setTag("");
e.preventDefault();
}
}
}}
labelPlacement="outside"
className="w-full h-fit"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.tags && (
<p className="text-red-400 text-sm mb-3">{errors.tags?.message}</p>
)}
{!isDetail && username === "admin-mabes" && (
<>
<p className="text-sm">Ubah Waktu Pembuatan</p>
<div className="flex flex-row gap-2">
<Popover
placement="bottom"
classNames={{ content: ["!bg-transparent", "p-0"] }}
>
<PopoverTrigger>
<Button
className="w-1/3 !h-[30px] lg:h-[40px] border-1 rounded-lg text-black"
variant="bordered"
>
{startDateValue
? convertDateFormatNoTime(startDateValue)
: "-"}
</Button>
</PopoverTrigger>
<PopoverContent className="bg-transparent">
<Calendar
value={startDateValue}
onChange={setStartDateValue}
/>
</PopoverContent>
</Popover>
<Input
type="time"
variant="bordered"
className="w-fit "
value={timeValue}
onValueChange={setTimeValue}
classNames={{
inputWrapper: [
"border-1 rounded-lg !h-[30px] lg:h-[40px]",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
/>
</div>
</>
)}
</div>
<div className="flex flex-row justify-end gap-3 mb-10">
{isDetail &&
username === "admin-mabes" &&
(detailData?.statusId === 1 || detailData?.statusId === null) && (
<Button
color="primary"
type="button"
onPress={() => {
setApprovalStatus(2);
onOpen();
}}
>
Setujui
</Button>
)}
{isDetail &&
username === "admin-mabes" &&
(detailData?.statusId === 1 || detailData?.statusId === null) && (
<Button
color="danger"
type="button"
onPress={() => {
setApprovalStatus(3);
onOpen();
}}
>
Tolak
</Button>
)}
{!isDetail && (
<Button color="primary" type="submit">
Simpan
</Button>
)}
{isDetail &&
detailData?.isPublish === false &&
detailData?.statusId !== 1 &&
Number(userId) === detailData?.createdById && (
<Button color="primary" onPress={doPublish}>
Publish
</Button>
)}
{!isDetail && (
<Button
onPress={() => handleDelete(id)}
color="danger"
type="button"
>
<p className="text-white">Delete</p>
</Button>
)}
<Link href="/admin/article">
<Button color="danger" variant="bordered" type="button">
Kembali
</Button>
</Link>
</div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col">Approval</ModalHeader>
<ModalBody>
<p className="text-sm">
Status :
<span
className={
approvalStatus === 2 ? "text-primary" : "text-danger"
}
>
{" "}
{approvalStatus === 2 ? "Disetujui" : "Ditolak"}
</span>
</p>
<Textarea
labelPlacement="outside"
label="Pesan "
placeholder="Masukkan pesan"
variant="bordered"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
value={approvalMessage}
onValueChange={setApprovalMessage}
/>
</ModalBody>
<ModalFooter>
<Button color="primary" onPress={doApproval}>
Submit
</Button>
<Button
color="danger"
variant="light"
onPress={() => {
setApprovalMessage("");
onClose();
}}
>
Close
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
</div>
</form>
);
}