[QUDO-151] feat:approval histori, fix:status content table

This commit is contained in:
Rama Priyanto 2025-06-07 12:36:19 +07:00
parent 2e6a2beb5c
commit a8a9a987eb
10 changed files with 330 additions and 46 deletions

View File

@ -2,7 +2,7 @@ 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 { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -22,6 +22,8 @@ import { useTranslations } from "next-intl";
const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
const MySwal = withReactContent(Swal);
const userLevelId = getCookiesDecrypt("ulie");
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
@ -121,13 +123,24 @@ const useTableColumns = () => {
"menunggu review": "bg-orange-100 text-orange-600",
};
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const colors = [
"bg-orange-100 text-orange-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-blue-100 text-blue-600",
"bg-red-200 text-red-600",
];
const status =
isPublish || isPublishOnPolda ? "diterima" : "menunggu review";
const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
Number(row.original?.statusId) == 2 &&
row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
? "1"
: row.original?.statusId;
const statusStyles =
colors[Number(status)] || "bg-red-200 text-red-600";
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
return (
<Badge
@ -136,7 +149,18 @@ const useTableColumns = () => {
statusStyles
)}
>
{status}
{(Number(row.original?.statusId) == 2 &&
!row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(
`:${Number(userLevelId)}:`
) &&
Number(row.original?.creatorGroupLevelId) !=
Number(userLevelId)) ||
(Number(row.original?.statusId) == 1 &&
Number(row.original?.needApprovalFromLevel) ==
Number(userLevelId))
? "Menunggu Review"
: row.original?.statusName}{" "}
</Badge>
);
},

View File

@ -3,7 +3,7 @@ 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 { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -25,6 +25,8 @@ import { useTranslations } from "next-intl";
const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
const MySwal = withReactContent(Swal);
const userLevelId = getCookiesDecrypt("ulie");
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
@ -125,13 +127,24 @@ const useTableColumns = () => {
"menunggu review": "bg-orange-100 text-orange-600",
};
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const colors = [
"bg-orange-100 text-orange-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-blue-100 text-blue-600",
"bg-red-200 text-red-600",
];
const status =
isPublish || isPublishOnPolda ? "diterima" : "menunggu review";
const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
Number(row.original?.statusId) == 2 &&
row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
? "1"
: row.original?.statusId;
const statusStyles =
colors[Number(status)] || "bg-red-200 text-red-600";
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
return (
<Badge
@ -140,7 +153,18 @@ const useTableColumns = () => {
statusStyles
)}
>
{status}
{(Number(row.original?.statusId) == 2 &&
!row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(
`:${Number(userLevelId)}:`
) &&
Number(row.original?.creatorGroupLevelId) !=
Number(userLevelId)) ||
(Number(row.original?.statusId) == 1 &&
Number(row.original?.needApprovalFromLevel) ==
Number(userLevelId))
? "Menunggu Review"
: row.original?.statusName}{" "}
</Badge>
);
},

View File

@ -2,7 +2,7 @@ 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 { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -22,6 +22,8 @@ import { useTranslations } from "next-intl";
const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
const MySwal = withReactContent(Swal);
const userLevelId = getCookiesDecrypt("ulie");
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
@ -122,13 +124,24 @@ const useTableColumns = () => {
"menunggu review": "bg-orange-100 text-orange-600",
};
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const colors = [
"bg-orange-100 text-orange-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-blue-100 text-blue-600",
"bg-red-200 text-red-600",
];
const status =
isPublish || isPublishOnPolda ? "diterima" : "menunggu review";
const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
Number(row.original?.statusId) == 2 &&
row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
? "1"
: row.original?.statusId;
const statusStyles =
colors[Number(status)] || "bg-red-200 text-red-600";
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
return (
<Badge
@ -137,7 +150,18 @@ const useTableColumns = () => {
statusStyles
)}
>
{status}
{(Number(row.original?.statusId) == 2 &&
!row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(
`:${Number(userLevelId)}:`
) &&
Number(row.original?.creatorGroupLevelId) !=
Number(userLevelId)) ||
(Number(row.original?.statusId) == 1 &&
Number(row.original?.needApprovalFromLevel) ==
Number(userLevelId))
? "Menunggu Review"
: row.original?.statusName}{" "}
</Badge>
);
},

View File

@ -2,7 +2,7 @@ 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 { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -22,6 +22,7 @@ import { useTranslations } from "next-intl";
const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
const MySwal = withReactContent(Swal);
const userLevelId = getCookiesDecrypt("ulie");
const columns: ColumnDef<any>[] = [
{
@ -123,13 +124,24 @@ const useTableColumns = () => {
"menunggu review": "bg-orange-100 text-orange-600",
};
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const colors = [
"bg-orange-100 text-orange-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-blue-100 text-blue-600",
"bg-red-200 text-red-600",
];
const status =
isPublish || isPublishOnPolda ? "diterima" : "menunggu review";
const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
Number(row.original?.statusId) == 2 &&
row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
? "1"
: row.original?.statusId;
const statusStyles =
colors[Number(status)] || "bg-red-200 text-red-600";
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
return (
<Badge
@ -138,7 +150,18 @@ const useTableColumns = () => {
statusStyles
)}
>
{status}
{(Number(row.original?.statusId) == 2 &&
!row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(
`:${Number(userLevelId)}:`
) &&
Number(row.original?.creatorGroupLevelId) !=
Number(userLevelId)) ||
(Number(row.original?.statusId) == 1 &&
Number(row.original?.needApprovalFromLevel) ==
Number(userLevelId))
? "Menunggu Review"
: row.original?.statusName}{" "}
</Badge>
);
},

View File

@ -30,7 +30,10 @@ import {
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
detailMedia,
getDataApprovalByMediaUpload,
} from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon, Music } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
@ -58,6 +61,9 @@ import dynamic from "next/dynamic";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
import { useTranslations } from "next-intl";
import SuggestionModal from "@/components/modal/suggestions-modal";
import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -148,6 +154,7 @@ export default function FormAudioDetail() {
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false);
const [approval, setApproval] = useState<any>();
const onReady = (ws: any) => {
setWavesurfer(ws);
@ -281,9 +288,10 @@ export default function FormAudioDetail() {
file.secondaryUrl ? file.secondaryUrl : "default-audio.mp3"
);
console.log("audio", fileUrls);
setDetailThumb(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data);
}
}
initState();
@ -639,13 +647,21 @@ export default function FormAudioDetail() {
</div>
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">{t("suggestion-box")}(0)</p>
</div>
<SuggestionModal
id={Number(id)}
numberOfSuggestion={detail?.numberOfSuggestion}
/>
<div className="px-3 py-3 border mx-3">
<p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
<p>Komentar</p>
<p>{approval?.message}</p>
<p className="text-right text-sm">
{" "}
{approval?.approvalBy?.fullname} |{" "}
{formatDateToIndonesian(approval?.approvalDate)}
</p>
<ApprovalHistoryModal id={Number(id)} />
</div>
{/* {detail?.isPublish == false ? (
<div className="p-3">

View File

@ -31,7 +31,10 @@ import {
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
detailMedia,
getDataApprovalByMediaUpload,
} from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
@ -61,6 +64,8 @@ import { useRouter } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { UnitMapping } from "@/app/[locale]/(protected)/contributor/agenda-setting/unit-mapping";
import SuggestionModal from "@/components/modal/suggestions-modal";
import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -141,6 +146,7 @@ export default function FormImageDetail() {
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [approval, setApproval] = useState<any>();
const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]);
@ -262,6 +268,9 @@ export default function FormImageDetail() {
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data);
}
}
initState();
@ -638,6 +647,14 @@ export default function FormImageDetail() {
<div className="px-3 py-3 border mx-3">
<p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
<p>Komentar</p>
<p>{approval?.message}</p>
<p className="text-right text-sm">
{" "}
{approval?.approvalBy?.fullname} |{" "}
{formatDateToIndonesian(approval?.approvalDate)}
</p>
<ApprovalHistoryModal id={Number(id)} />
</div>
{/* {detail?.isPublish == false ? (
<div className="p-3">

View File

@ -30,7 +30,10 @@ import {
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
detailMedia,
getDataApprovalByMediaUpload,
} from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
@ -56,6 +59,9 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
import SuggestionModal from "@/components/modal/suggestions-modal";
import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -143,6 +149,8 @@ export default function FormTeksDetail() {
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [approval, setApproval] = useState<any>();
let fileTypeId = "3";
const {
@ -262,6 +270,9 @@ export default function FormTeksDetail() {
fileName: file.fileName,
}));
setDetailThumb(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data);
}
}
initState();
@ -659,13 +670,21 @@ export default function FormTeksDetail() {
</div>
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">{t("suggestion-box")} (0)</p>
</div>
<SuggestionModal
id={Number(id)}
numberOfSuggestion={detail?.numberOfSuggestion}
/>
<div className="px-3 py-3 border mx-3">
<p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
<p>Komentar</p>
<p>{approval?.message}</p>
<p className="text-right text-sm">
{" "}
{approval?.approvalBy?.fullname} |{" "}
{formatDateToIndonesian(approval?.approvalDate)}
</p>
<ApprovalHistoryModal id={Number(id)} />
</div>
{/* {detail?.isPublish == false ? (
<div className="p-3">

View File

@ -30,7 +30,10 @@ import {
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
detailMedia,
getDataApprovalByMediaUpload,
} from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
@ -57,6 +60,9 @@ import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useRouter } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import SuggestionModal from "@/components/modal/suggestions-modal";
import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -142,6 +148,7 @@ export default function FormVideoDetail() {
const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [approval, setApproval] = useState<any>();
let fileTypeId = "2";
@ -252,6 +259,9 @@ export default function FormVideoDetail() {
files.url ? files.url : "default-image.jpg"
);
setDetailVideo(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data);
}
}
initState();
@ -614,13 +624,21 @@ export default function FormVideoDetail() {
</div>
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">{t("suggestion-box")} (0)</p>
</div>
<SuggestionModal
id={Number(id)}
numberOfSuggestion={detail?.numberOfSuggestion}
/>
<div className="px-3 py-3 border mx-3">
<p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
<p>Komentar</p>
<p>{approval?.message}</p>
<p className="text-right text-sm">
{" "}
{approval?.approvalBy?.fullname} |{" "}
{formatDateToIndonesian(approval?.approvalDate)}
</p>
<ApprovalHistoryModal id={Number(id)} />
</div>
{/* {detail?.isPublish == false ? (
<div className="p-3">

View File

@ -0,0 +1,110 @@
"use client";
import { Fragment, useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTrigger,
} from "../ui/dialog";
import { getDataApprovalHistory } from "@/service/curated-content/curated-content";
import { ChevronRightIcon } from "lucide-react";
export default function ApprovalHistoryModal(props: { id: number }) {
const { id } = props;
const [history, setHistory] = useState<any>([]);
useEffect(() => {
getApprovalHistory();
}, []);
const getApprovalHistory = async () => {
const res = await getDataApprovalHistory(id);
console.log("res hstro", res?.data?.data);
if (res?.data?.data) {
setHistory(res?.data?.data);
}
};
return (
<Dialog>
<DialogTrigger>
<a className="mt-2 hover:underline text-primary">
Lihat Riwayat Approval
</a>
</DialogTrigger>
<DialogContent size="md" className="px-1 lg:px-4">
<div className="grid grid-cols-6 font-light text-sm">
<div className="col-span-2 flex justify-center">
<p className="px-12 py-4 rounded-full bg-cyan-200 ">Upload</p>
</div>
<div className="col-span-2 col-start-1 flex justify-center">
<p className="border-l-2 border-black h-8"></p>
</div>
{history?.length &&
history?.map((list: any, index: number) => (
<Fragment key={list.id}>
{" "}
<div
className={`col-span-2 col-start-1 p-4 rounded-lg flex flex-col gap1 ${
list.status.id == 2
? "bg-cyan-500"
: list.status.id == 4
? "bg-red-500"
: "bg-yellow-500"
}`}
>
<p className="text-white font-semibold mb-2">
{list.status.name}
</p>
<div
className={`w-full rounded-lg flex flex-col p-4 ${
list.status.id == 2
? "bg-cyan-200"
: list.status.id == 4
? "bg-red-50"
: "bg-yellow-100"
}`}
>
<p>Level: {list.levelNumber}</p>
<p>Direview oleh: {list.approvalBy.fullname}</p>
</div>
</div>
<div className="col-span-1 flex justify-center items-center">
<ChevronRightIcon />
</div>
<div className="col-span-3 flex items-center">
<div
className={`flex flex-col p-4 w-full rounded-lg ${
list.status.id == 2
? "bg-cyan-200"
: list.status.id == 4
? "bg-red-50"
: "bg-yellow-100"
}`}
>
<p>Catatan :</p>
<p>{list.message}</p>
</div>
</div>
{index !== history.length - 1 && (
<div className="col-span-2 col-start-1 flex justify-center">
<p className="border-l-2 border-black h-8"></p>
</div>
)}
</Fragment>
))}
{history.length > 0 && history[history.length - 1].status.id == 2 && (
<>
<div className="col-span-2 col-start-1 flex justify-center">
<p className="border-l-2 border-black h-8"></p>
</div>
<div className="col-span-2 col-start-1 flex justify-center">
<p className="px-12 py-4 rounded-full bg-green-300 ">Publish</p>
</div>
</>
)}
</div>
</DialogContent>
</Dialog>
);
}

View File

@ -50,3 +50,12 @@ export async function deleteSuggestion(id: number | string) {
const url = `media/suggestion?id=${id}`;
return httpDeleteInterceptor(url);
}
export async function getDataApprovalByMediaUpload(id: number | string) {
const url = `media/approval?mediaUploadId=${id}`;
return httpGetInterceptor(url);
}
export async function getDataApprovalHistory(id: number | string) {
const url = `media/approval/history?id=${id}`;
return httpGetInterceptor(url);
}