feat:approval

This commit is contained in:
Rama Priyanto 2025-02-17 23:32:20 +07:00
parent 33fcdd670c
commit e4dd1da844
7 changed files with 184 additions and 28 deletions

View File

@ -18,13 +18,22 @@ import {
deleteArticleFiles, deleteArticleFiles,
getArticleByCategory, getArticleByCategory,
getArticleById, getArticleById,
submitApproval,
updateArticle, updateArticle,
uploadArticleFile, uploadArticleFile,
uploadArticleThumbnail, uploadArticleThumbnail,
} from "@/service/article"; } from "@/service/article";
import ReactSelect from "react-select"; import ReactSelect from "react-select";
import makeAnimated from "react-select/animated"; import makeAnimated from "react-select/animated";
import { Chip } from "@heroui/react"; import {
Chip,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
useDisclosure,
} from "@heroui/react";
import GenerateSingleArticleForm from "./generate-ai-single-form"; import GenerateSingleArticleForm from "./generate-ai-single-form";
import { htmlToString } from "@/utils/global"; import { htmlToString } from "@/utils/global";
import { close, error, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
@ -112,6 +121,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
null null
); );
const [thumbnailValidation, setThumbnailValidation] = useState(""); 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 { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
@ -151,6 +164,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
loading(); loading();
const res = await getArticleById(id); const res = await getArticleById(id);
const data = res.data?.data; const data = res.data?.data;
setDetailData(data);
setValue("title", data?.title); setValue("title", data?.title);
setValue("slug", data?.slug); setValue("slug", data?.slug);
setValue("description", data?.htmlDescription); setValue("description", data?.htmlDescription);
@ -161,15 +175,14 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
setupInitCategory(data?.categories); setupInitCategory(data?.categories);
close(); close();
console.log("Data Aritcle", data?.files);
} }
const setupInitCategory = (data: any) => { const setupInitCategory = (data: any) => {
const temp: CategoryType[] = []; const temp: CategoryType[] = [];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data?.length; i++) {
const datas = listCategory.filter((a) => a.id == data[i].id); const datas = listCategory.filter((a) => a.id == data[i].id);
if (datas[0]) { if (datas[0]) {
temp.push(datas[0]); // Hanya tambahkan jika datas[0] ada temp.push(datas[0]);
} }
} }
setValue("category", temp as [CategoryType, ...CategoryType[]]); setValue("category", temp as [CategoryType, ...CategoryType[]]);
@ -373,6 +386,49 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
} }
}; };
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();
}
});
};
return ( return (
<form <form
className="flex flex-col lg:flex-row gap-8 text-black" className="flex flex-col lg:flex-row gap-8 text-black"
@ -767,6 +823,30 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
)} )}
</div> </div>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
{isDetail && detailData?.statusId === 1 && (
<Button
color="primary"
type="button"
onPress={() => {
setApprovalStatus(2);
onOpen();
}}
>
Setujui
</Button>
)}
{isDetail && detailData?.statusId === 1 && (
<Button
color="danger"
type="button"
onPress={() => {
setApprovalStatus(3);
onOpen();
}}
>
Tolak
</Button>
)}
{!isDetail && ( {!isDetail && (
<Button color="primary" type="submit"> <Button color="primary" type="submit">
Publish Publish
@ -783,6 +863,57 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
</Button> </Button>
</Link> </Link>
</div> </div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col">Approval</ModalHeader>
<ModalBody>
<p className="text-sm">
Status :
<span
className={
approvalStatus === 3 ? "text-primary" : "text-danger"
}
>
{" "}
{approvalStatus === 3 ? "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> </div>
</form> </form>
); );

View File

@ -104,10 +104,10 @@ export default function ENewsPolri() {
</div> </div>
</div> </div>
<Link <Link
className="flex items-center gap-2 text-[#DD8306] mt-3" className="flex items-center gap-2 text-[#bb3523] mt-3"
href="/e-majalah-polri/daftar-majalah" href="/e-majalah-polri/daftar-majalah"
> >
Lihat Semua <ChevronRightIcon color="[#DD8306]" /> Lihat Semua <ChevronRightIcon color="[#bb3523]" />
</Link> </Link>
</div> </div>
); );

View File

@ -40,7 +40,12 @@ export default function HeaderNews() {
}, []); }, []);
async function getArticle() { async function getArticle() {
const req = { page: 1, search: "", limit: "10", sort: "desc" }; const req = {
page: 1,
search: "",
limit: "10",
sort: "desc",
};
const response = await getListArticle(req); const response = await getListArticle(req);
setArticle(response?.data?.data); setArticle(response?.data?.data);
} }

View File

@ -46,6 +46,7 @@ const columns = [
{ name: "Kategori", uid: "category" }, { name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" }, { name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" }, { name: "Kreator", uid: "createdByName" },
// { name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" }, { name: "Aksi", uid: "actions" },
]; ];
@ -89,7 +90,6 @@ export default function ArticleTable() {
} }
async function initState() { async function initState() {
console.log("seeecle", Array.from(selectedCategories));
const req = { const req = {
limit: showData, limit: showData,
page: page, page: page,
@ -98,6 +98,8 @@ export default function ArticleTable() {
startDateValue.startDate === null ? "" : startDateValue.startDate, startDateValue.startDate === null ? "" : startDateValue.startDate,
endDate: startDateValue.endDate === null ? "" : startDateValue.endDate, endDate: startDateValue.endDate === null ? "" : startDateValue.endDate,
category: Array.from(selectedCategories).join(","), category: Array.from(selectedCategories).join(","),
sort: "desc",
sortBy: "created_at",
}; };
const res = await getListArticle(req); const res = await getListArticle(req);
getTableNumber(parseInt(showData), res.data?.data); getTableNumber(parseInt(showData), res.data?.data);
@ -148,25 +150,21 @@ export default function ArticleTable() {
const renderCell = useCallback( const renderCell = useCallback(
(article: any, columnKey: Key) => { (article: any, columnKey: Key) => {
const cellValue = article[columnKey as keyof any]; const cellValue = article[columnKey as keyof any];
const statusColorMap: Record<string, ChipProps["color"]> = {
active: "primary",
cancel: "danger",
pending: "success",
};
switch (columnKey) { switch (columnKey) {
case "status": case "isPublish":
return ( return (
<Chip // <Chip
className="capitalize " // className="capitalize "
color={statusColorMap[article.status]} // color={statusColorMap[article.status]}
size="lg" // size="lg"
variant="flat" // variant="flat"
> // >
<div className="flex flex-row items-center gap-2 justify-center"> // <div className="flex flex-row items-center gap-2 justify-center">
{article.status} // {article.status}
</div> // </div>
</Chip> // </Chip>
<p>{article.isPublish ? "Publish" : "Draft"}</p>
); );
case "createdAt": case "createdAt":
return <p>{convertDateFormat(article.createdAt)}</p>; return <p>{convertDateFormat(article.createdAt)}</p>;

View File

@ -24,7 +24,7 @@ export function loading(msg?: any) {
timerProgressBar: true, timerProgressBar: true,
didOpen: () => { didOpen: () => {
MySwal.showLoading(); MySwal.showLoading();
timerInterval = setInterval(() => { }, 100); timerInterval = setInterval(() => {}, 100);
}, },
willClose: () => { willClose: () => {
clearInterval(timerInterval); clearInterval(timerInterval);
@ -37,6 +37,10 @@ export function error(msg?: any) {
icon: "error", icon: "error",
title: "Failed...", title: "Failed...",
text: msg || "Unknown Error", text: msg || "Unknown Error",
customClass: {
popup: "custom-popup",
confirmButton: "custom-button",
},
}); });
} }
@ -91,5 +95,3 @@ export function successToast(title: string, text: string) {
text: text, text: text,
}); });
} }

View File

@ -84,7 +84,7 @@ export async function getArticleByCategory() {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGet(`/article-categories?limit=50`, headers); return await httpGet(`/article-categories?limit=200`, headers);
} }
export async function getCategoryPagination(data: any) { export async function getCategoryPagination(data: any) {
const headers = { const headers = {
@ -139,3 +139,15 @@ export async function getStatisticSummary() {
}; };
return await httpGet(`/articles/statistic/summary`, headers); return await httpGet(`/articles/statistic/summary`, headers);
} }
export async function submitApproval(data: {
articleId: number;
message: string;
statusId: number;
}) {
const headers = {
"content-type": "multipart/form-data",
Authorization: `Bearer ${token}`,
};
return await httpPost(`/article-approvals`, headers, data);
}

View File

@ -96,3 +96,11 @@ main {
.komdigi-styling #gpr-kominfo-widget-body { .komdigi-styling #gpr-kominfo-widget-body {
height: 67vh !important; height: 67vh !important;
} }
.custom-popup {
color: black;
}
.custom-button {
color: black !important;
}