feat:approval content

This commit is contained in:
Rama Priyanto 2025-01-02 11:53:30 +07:00
parent 99ea4f86d2
commit 15967c8bff
3 changed files with 323 additions and 70 deletions

View File

@ -11,6 +11,7 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/i18n/routing";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -154,12 +155,12 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<a href="/en/task/detail/[id]"> <Link href={`/contributor/content/image/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <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" /> <Eye className="w-4 h-4 me-1.5" />
View View
</DropdownMenuItem> </DropdownMenuItem>
</a> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <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" /> <Trash2 className="w-4 h-4 me-1.5" />
Delete Delete

View File

@ -27,10 +27,23 @@ import {
createMedia, createMedia,
getTagsBySubCategoryId, getTagsBySubCategoryId,
listEnableCategory, listEnableCategory,
rejectFiles,
submitApproval,
} from "@/service/content/content"; } from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content"; import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react"; import { MailIcon } from "lucide-react";
import { Icon } from "@iconify/react/dist/iconify.js";
import { getCookiesDecrypt } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/config/swal";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -46,6 +59,13 @@ type Category = {
name: string; name: string;
}; };
type FileType = {
id: number;
url: string;
thumbnailFileUrl: string;
fileName: string;
};
type Detail = { type Detail = {
id: string; id: string;
title: string; title: string;
@ -60,12 +80,20 @@ type Detail = {
thumbnailLink: string; thumbnailLink: string;
tags: string; tags: string;
statusName: string; statusName: string;
isPublish: boolean;
needApprovalFromLevel: number;
files: FileType[];
uploadedById: number;
}; };
export default function FormImageDetail() { export default function FormImageDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const [modalOpen, setModalOpen] = useState(false);
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
console.log(id); console.log(id);
const editor = useRef(null); const editor = useRef(null);
@ -75,21 +103,18 @@ export default function FormImageDetail() {
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const [status, setStatus] = useState("");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<Detail>(); const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]); const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [description, setDescription] = useState("");
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [files, setFiles] = useState<FileType[]>([]);
allUnit: false, const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
mabes: false, const [isMabesApprover, setIsMabesApprover] = useState(false);
polda: false,
polres: false,
});
let fileTypeId = "1"; let fileTypeId = "1";
@ -113,21 +138,16 @@ export default function FormImageDetail() {
// } // }
// }; // };
const handleRemoveTag = (index: any) => { useEffect(() => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); if (
}; userLevelId != undefined &&
roleId != undefined &&
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => { userLevelId == "216" &&
if (event.target.files) { roleId == "3"
const files = Array.from(event.target.files); ) {
setSelectedFiles((prevImages: any) => [...prevImages, ...files]); setIsMabesApprover(true);
console.log("DATAFILE::", selectedFiles);
} }
}; }, [userLevelId, roleId]);
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
@ -173,7 +193,8 @@ export default function FormImageDetail() {
if (id) { if (id) {
const response = await detailMedia(id); const response = await detailMedia(id);
const details = response.data?.data; const details = response.data?.data;
console.log("detail", details);
setFiles(details?.files);
setDetail(details); setDetail(details);
if (details.publishedForObject) { if (details.publishedForObject) {
@ -197,42 +218,21 @@ export default function FormImageDetail() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
const save = async (data: ImageSchema) => { const actionApproval = (e: string) => {
const requestData = { setStatus(e);
...data, setModalOpen(true);
title: data.title, setDescription("");
description: data.description,
htmlDescription: data.description,
fileTypeId,
categoryId: selectedTarget,
subCategoryId: selectedTarget,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: "6",
creatorName: data.creatorName,
tags: "siap",
isYoutube: false,
isInternationalMedia: false,
}; };
const response = await createMedia(requestData); const submit = async () => {
console.log("Form Data Submitted:", requestData); if (
(description?.length > 1 && Number(status) == 3) ||
Number(status) == 2 ||
Number(status) == 4
) {
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Simpan Approval",
text: "Data berhasil disimpan.", text: "",
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", icon: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonColor: "#d33", cancelButtonColor: "#d33",
@ -240,13 +240,54 @@ export default function FormImageDetail() {
confirmButtonText: "Simpan", confirmButtonText: "Simpan",
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
save(data); save();
} }
}); });
}
}; };
async function save() {
const data = {
mediaUploadId: id,
statusId: status,
message: description,
files: [],
// files: isMabesApprover ? getPlacement() : [],
};
loading();
const response = await submitApproval(data);
if (response?.error) {
error(response.message);
return false;
}
const dataReject = {
listFiles: rejectedFiles,
};
const resReject = await rejectFiles(dataReject);
if (resReject?.error) {
error(resReject.message);
return false;
}
close();
return false;
}
function handleDeleteFileApproval(id: number) {
const selectedFiles = files.filter((file) => file.id != id);
setFiles(selectedFiles);
const rejects = rejectedFiles;
rejects.push(id);
setRejectedFiles(rejects);
}
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form>
{detail !== undefined ? ( {detail !== undefined ? (
<div className="flex lg:flex-row gap-10"> <div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
@ -319,6 +360,14 @@ export default function FormImageDetail() {
</p> </p>
)} )}
</div> </div>
<img src={detail?.files[0]?.url} className="w-[90%] my-5" />
<div className="flex flex-row gap-3">
{detail?.files.map((file) => (
<div key={file.id}>
<img src={file.url} className="w-[25%]" />
</div>
))}
</div>
</div> </div>
</div> </div>
</Card> </Card>
@ -417,6 +466,196 @@ export default function FormImageDetail() {
<p>Keterangan:</p> <p>Keterangan:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p> <p className="text-sm text-slate-400">{detail?.statusName}</p>
</div> </div>
{/* {detail?.isPublish == false ? (
<div className="p-3">
<Button className="bg-blue-600">Publish</Button>
</div>
) : (
""
)} */}
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
Number(detail?.uploadedById) == Number(userId) ? (
""
) : (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Setujui
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
Tolak
</Button>
</div>
)
) : (
""
)}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle>
</DialogHeader>
{status == "2"
? files?.map((file) => (
<div
key={file.id}
className="flex flex-row gap-2 items-center"
>
<img src={file.url} className="w-[200px]" />
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between text-sm">
{file.fileName}
<a>
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Semua
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Nasional
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Wilayah
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Internasional
</label>
</div>
</div>
</div>
</div>
))
: ""}
<div className="flex flex-col gap-4">
<Textarea
placeholder="Type your message here."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
{status == "3" || status == "4" ? (
<div className="flex flex-row gap-2">
<Badge
color={
description === "Kualitas media kurang baik"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() =>
setDescription("Kualitas media kurang baik")
}
>
Kualitas media kurang baik
</Badge>
<Badge
color={
description === "Deskripsi kurang lengkap"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() =>
setDescription("Deskripsi kurang lengkap")
}
>
Deskripsi kurang lengkap
</Badge>
<Badge
color={
description === "Judul kurang tepat"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Judul kurang tepat")}
>
Judul kurang tepat
</Badge>
</div>
) : (
<div className="flex flex-row gap-2">
<Badge
color={
description === "Konten sangat bagus"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Konten sangat bagus")}
>
Konten sangat bagus
</Badge>
<Badge
color={
description === "Konten menarik"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Konten menarik")}
>
Konten menarik
</Badge>
</div>
)}
<DialogFooter>
<Button type="button" color="primary">
Submit
</Button>
<Button
type="button"
color="destructive"
onClick={() => setModalOpen(false)}
>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card> </Card>
</div> </div>
</div> </div>

View File

@ -1,4 +1,7 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service"; import {
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
// export async function listDataAll( // export async function listDataAll(
// isForSelf, // isForSelf,
@ -174,3 +177,13 @@ export async function convertSPIT(data: any) {
const url = "media/spit/convert"; const url = "media/spit/convert";
return httpPostInterceptor(url, data); return httpPostInterceptor(url, data);
} }
export async function submitApproval(data: any) {
const url = "media/approval";
return httpPostInterceptor(url, data);
}
export async function rejectFiles(data: any) {
const url = "media/file/rejects";
return httpPostInterceptor(url, data);
}