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";
import { Button } from "@/components/ui/button";
import { format } from "date-fns";
import { Link } from "@/i18n/routing";
const columns: ColumnDef<any>[] = [
{
@ -154,12 +155,12 @@ const columns: ColumnDef<any>[] = [
</Button>
</DropdownMenuTrigger>
<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">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</a>
</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

View File

@ -27,10 +27,23 @@ import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
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({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -46,6 +59,13 @@ type Category = {
name: string;
};
type FileType = {
id: number;
url: string;
thumbnailFileUrl: string;
fileName: string;
};
type Detail = {
id: string;
title: string;
@ -60,12 +80,20 @@ type Detail = {
thumbnailLink: string;
tags: string;
statusName: string;
isPublish: boolean;
needApprovalFromLevel: number;
files: FileType[];
uploadedById: number;
};
export default function FormImageDetail() {
const MySwal = withReactContent(Swal);
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 };
console.log(id);
const editor = useRef(null);
@ -75,21 +103,18 @@ export default function FormImageDetail() {
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [status, setStatus] = useState("");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [description, setDescription] = useState("");
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isMabesApprover, setIsMabesApprover] = useState(false);
let fileTypeId = "1";
@ -113,21 +138,16 @@ export default function FormImageDetail() {
// }
// };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
console.log("DATAFILE::", selectedFiles);
useEffect(() => {
if (
userLevelId != undefined &&
roleId != undefined &&
userLevelId == "216" &&
roleId == "3"
) {
setIsMabesApprover(true);
}
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
}, [userLevelId, roleId]);
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
@ -173,7 +193,8 @@ export default function FormImageDetail() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
console.log("detail", details);
setFiles(details?.files);
setDetail(details);
if (details.publishedForObject) {
@ -197,56 +218,76 @@ export default function FormImageDetail() {
initState();
}, [refresh, setValue]);
const save = async (data: ImageSchema) => {
const requestData = {
...data,
title: data.title,
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 actionApproval = (e: string) => {
setStatus(e);
setModalOpen(true);
setDescription("");
};
const submit = async () => {
if (
(description?.length > 1 && Number(status) == 3) ||
Number(status) == 2 ||
Number(status) == 4
) {
MySwal.fire({
title: "Simpan Approval",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save();
}
});
}
};
async function save() {
const data = {
mediaUploadId: id,
statusId: status,
message: description,
files: [],
// files: isMabesApprover ? getPlacement() : [],
};
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
loading();
const response = await submitApproval(data);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
});
};
if (response?.error) {
error(response.message);
return false;
}
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 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 (
<form onSubmit={handleSubmit(onSubmit)}>
<form>
{detail !== undefined ? (
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
@ -319,6 +360,14 @@ export default function FormImageDetail() {
</p>
)}
</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>
</Card>
@ -417,6 +466,196 @@ export default function FormImageDetail() {
<p>Keterangan:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
</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>
</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(
// isForSelf,
@ -174,3 +177,13 @@ export async function convertSPIT(data: any) {
const url = "media/spit/convert";
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);
}