fix: all error in admin

This commit is contained in:
Sabda Yagra 2026-01-07 23:03:38 +07:00
parent e26bd85880
commit 46964678c2
10 changed files with 228 additions and 85 deletions

View File

@ -66,7 +66,10 @@ const useTableColumns = () => {
accessorKey: "createdAt",
header: "Upload Date",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as string | number | undefined;
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
@ -98,7 +101,8 @@ const useTableColumns = () => {
cell: ({ row }) => {
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
const creatorGroupParentLevelId =
row.original.creatorGroupParentLevelId;
let displayText = "-";
if (isPublish && !isPublishOnPolda) {
@ -124,40 +128,101 @@ const useTableColumns = () => {
);
},
},
// {
// accessorKey: "statusName",
// header: "Status",
// cell: ({ row }) => {
// const statusId = Number(row.original?.statusId);
// const reviewedAtLevel = row.original?.reviewedAtLevel || "";
// const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId);
// const needApprovalFromLevel = Number(row.original?.needApprovalFromLevel);
// const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
// const isCreator = creatorGroupLevelId === Number(userLevelId);
// const isWaitingForReview = statusId === 2 && !userHasReviewed && !isCreator;
// const isApprovalNeeded = statusId === 1 && needApprovalFromLevel === Number(userLevelId);
// const label =
// isWaitingForReview || isApprovalNeeded
// ? "Menunggu Review"
// : statusId === 2
// ? "Diterima"
// : row.original?.statusName;
// const colors: Record<string, string> = {
// "Menunggu Review": "bg-orange-100 text-orange-600",
// Diterima: "bg-green-100 text-green-600",
// default: "bg-red-200 text-red-600",
// };
// const statusStyles = colors[label] || colors.default;
// return (
// <Badge className={cn("rounded-full px-5 w-full whitespace-nowrap", statusStyles)}>
// {label}
// </Badge>
// );
// },
// },
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusId = Number(row.original?.statusId);
const reviewedAtLevel = row.original?.reviewedAtLevel || "";
const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId);
const needApprovalFromLevel = Number(row.original?.needApprovalFromLevel);
const {
statusId,
statusName,
isPublish,
reviewedAtLevel = "",
creatorGroupLevelId,
needApprovalFromLevel,
} = row.original;
const userLevelId = Number(getCookiesDecrypt("ulie"));
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
const isCreator = creatorGroupLevelId === Number(userLevelId);
const isCreator = Number(creatorGroupLevelId) === userLevelId;
const isWaitingForReview = statusId === 2 && !userHasReviewed && !isCreator;
const isApprovalNeeded = statusId === 1 && needApprovalFromLevel === Number(userLevelId);
if (isPublish) {
return (
<div className="flex items-center justify-center w-full h-full">
<Badge className="flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 bg-green-100 text-green-700 text-center whitespace-nowrap">
Published
</Badge>
</div>
);
}
let label = statusName || "Menunggu Review";
const label =
isWaitingForReview || isApprovalNeeded
? "Menunggu Review"
: statusId === 2
? "Diterima"
: row.original?.statusName;
if (statusId === 2 && !userHasReviewed && !isCreator) {
label = "Menunggu Review";
} else if (statusId === 1 && needApprovalFromLevel === userLevelId) {
label = "Menunggu Review";
} else if (statusId === 2) {
label = "Diterima";
}
const colors: Record<string, string> = {
"Menunggu Review": "bg-orange-100 text-orange-600",
Diterima: "bg-green-100 text-green-600",
default: "bg-red-200 text-red-600",
Diterima: "bg-blue-100 text-blue-600",
Published: "bg-green-100 text-green-700",
Unknown: "bg-gray-100 text-gray-600",
default: "bg-gray-100 text-gray-600",
};
const statusStyles = colors[label] || colors.default;
return (
<Badge className={cn("rounded-full px-5 w-full whitespace-nowrap", statusStyles)}>
{label}
</Badge>
<div className="flex items-center justify-center w-full h-full">
<Badge
className={cn(
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
colors[label] || colors.default
)}
>
{label}
</Badge>
</div>
);
},
},
@ -215,7 +280,9 @@ const useTableColumns = () => {
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(Number(userLevelId) === 216 && Number(roleId) === 3);
setIsMabesApprover(
Number(userLevelId) === 216 && Number(roleId) === 3
);
}
}, [userLevelId, roleId]);
@ -224,7 +291,7 @@ const useTableColumns = () => {
<DropdownMenuTrigger asChild>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent cursor-pointer"
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
@ -235,22 +302,22 @@ const useTableColumns = () => {
href={`/admin/content/image/detail/${row.original.id}`}
className="hover:text-black"
>
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none">
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
{/* {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( */}
<Link href={`/admin/content/image/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
<Link href={`/admin/content/image/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
{/* )} */}
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none cursor-pointer"
>
<Trash2 className="w-4 h-4 me-1.5" />
Delete

View File

@ -176,7 +176,7 @@ const usePendingApprovalColumns = () => {
<DropdownMenuTrigger asChild>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent cursor-pointer"
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
@ -187,7 +187,7 @@ const usePendingApprovalColumns = () => {
href={`/admin/content/image/detail/${row.original.id}`}
className="hover:text-black"
>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none cursor-pointer hover:bg-slate-200">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>

View File

@ -84,7 +84,7 @@ const TableImage = () => {
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
const [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
@ -235,7 +235,7 @@ const TableImage = () => {
totalPage: Number(showData),
title: search || undefined,
categoryId: categoryFilter ? Number(categoryFilter) : undefined,
typeId: 1, // image content typeoriginalRows
typeId: 1,
statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined,
startDate: formattedStartDate || undefined,
endDate: formattedEndDate || undefined,

View File

@ -27,7 +27,7 @@ const ReactTableImagePage = () => {
</div>
<div className="flex-none">
<Link href={"/admin/content/image/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow cursor-pointer">
<UploadIcon size={18} className="mr-2" />
Create Image
</Button>

View File

@ -323,12 +323,8 @@ export default function FormImageDetail() {
try {
const response = await getArticleDetail(Number(id));
const details = response?.data?.data;
console.log("detail", details);
// Map the new API response to the expected format
const mappedDetail: Detail = {
...details,
// Map legacy fields for backward compatibility
category:
details.categories && details.categories.length > 0
? {
@ -339,19 +335,17 @@ export default function FormImageDetail() {
creatorName: details.createdByName,
thumbnailLink: details.thumbnailUrl,
statusName: getStatusName(details.statusId),
needApprovalFromLevel: 0, // This might need to be updated based on your business logic
needApprovalFromLevel: 0,
uploadedById: details.createdById,
files: details.files || [],
};
// Map files from new API structure to expected format
const mappedFiles = (mappedDetail.files || []).map((file: any) => ({
id: file.id,
url: file.fileUrl || file.url,
thumbnailFileUrl:
file.fileThumbnail || file.thumbnailFileUrl || file.fileUrl,
fileName: file.fileName || file.fileName,
// Keep original API fields for reference
...file,
}));
@ -360,7 +354,7 @@ export default function FormImageDetail() {
if (mappedFiles && mappedFiles.length > 0) {
setMain({
type: "image", // Default type for articles
type: "image",
url: mappedFiles[0]?.url || mappedDetail.thumbnailUrl,
names: mappedFiles[0]?.fileName || "image",
format: getFileExtension(mappedFiles[0]?.fileName || "jpg"),
@ -368,19 +362,22 @@ export default function FormImageDetail() {
setupPlacementCheck(mappedFiles.length);
}
// Set the selected target to the category ID from details
setSelectedTarget(String(mappedDetail.categoryId));
const fileUrls = mappedFiles.map(
(file: any) =>
file.thumbnailFileUrl ||
file.url ||
mappedDetail.thumbnailUrl ||
"default-image.jpg"
const fileUrls = (mappedFiles || []).map(
(file) => file.thumbnailFileUrl || file.url || "default-image.jpg"
);
setDetailThumb(fileUrls);
// Note: You might need to update this API call as well
if (details?.publishedForObject?.length > 0) {
const publisherIds = details.publishedForObject
.map((obj: any) => Number(obj.id))
.filter((id: number) => id === 5 || id === 6);
setSelectedPublishers(publisherIds);
}
const approvals = await getDataApprovalByMediaUpload(mappedDetail.id);
setApproval(approvals?.data?.data);
} catch (error) {
@ -391,7 +388,6 @@ export default function FormImageDetail() {
initState();
}, [refresh, setValue]);
// Helper function to get status name from status ID
const getStatusName = (statusId: number): string => {
const statusMap: { [key: number]: string } = {
1: "Menunggu Review",
@ -669,7 +665,7 @@ export default function FormImageDetail() {
navigation={false}
className="h-[480px] object-cover w-full"
>
{detailThumb?.map((data: any, index: number) => (
{/* {detailThumb?.map((data: any, index: number) => (
<SwiperSlide key={index}>
<img
className="h-[480px] max-w-[600px] rounded-md object-cover mx-auto border-2"
@ -677,6 +673,15 @@ export default function FormImageDetail() {
alt={`Image ${index + 1}`}
/>
</SwiperSlide>
))} */}
{detailThumb?.map((url: string, index: number) => (
<SwiperSlide key={index}>
<img
src={url}
alt={`Image ${index + 1}`}
className="h-[480px] max-w-[600px] rounded-md object-cover mx-auto border-2"
/>
</SwiperSlide>
))}
</Swiper>
<div className="mt-2 mx-auto min-w-fit max-w-[600px]">
@ -761,24 +766,34 @@ export default function FormImageDetail() {
<div className="flex flex-col gap-2 space-y-2">
<Label>Publish Target</Label>
<div className="flex gap-2 items-center">
<Checkbox
{/* <Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
className="border"
/> */}
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
disabled
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
{/* <Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
className="border"
/> */}
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
disabled
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
</div>
</div>
@ -847,7 +862,7 @@ export default function FormImageDetail() {
)} */}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="max-h-[600px]">
<DialogContent className="max-h-[600px] w-full">
<DialogHeader>
<DialogTitle>Leave Comment</DialogTitle>
</DialogHeader>
@ -859,7 +874,7 @@ export default function FormImageDetail() {
className="flex flex-row gap-5 items-center w-full"
>
<div className="w-[200px] h-[100px] flex justify-center items-center">
<img
{/* <img
key={index}
alt={file.fileAlt || `files-${index + 1}`}
src={file.url}
@ -867,6 +882,19 @@ export default function FormImageDetail() {
className={`h-[100px] object-cover ${
portraitMap[index] ? "w-auto" : "!w-[200px]"
}`}
/> */}
<img
alt={file.fileAlt || `files-${index + 1}`}
src={
file.fileUrl ||
file.url ||
file.fileThumbnail ||
file.thumbnailFileUrl
}
onLoad={(e) => handleImageLoad(e, index)}
className={`h-[100px] object-contain ${
portraitMap[index] ? "w-auto" : "w-[200px]"
}`}
/>
</div>
<div className="flex flex-col gap-2 w-full">

View File

@ -378,6 +378,7 @@ export default function FormImage() {
};
const res = await generateDataArticle(request);
console.log("AI RESPONSE FULL:", res);
close();
if (res?.error) {
@ -385,7 +386,12 @@ export default function FormImage() {
return false;
}
const newArticleId = res?.data?.data?.id;
// const newArticleId = res?.data?.data?.id;
const newArticleId =
res?.data?.data?.id ||
res?.data?.data?.articleId ||
res?.data?.data?.uuid;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {

View File

@ -94,7 +94,7 @@ export default function SignUp() {
// Kontributor (sementara ikut umum)
if (role === "kontributor") {
await handleCreateUserUmum(e);
await handleCreateUserKontributor(e);
return;
}
@ -134,6 +134,59 @@ export default function SignUp() {
return password.length >= 8;
};
const handleCreateUserKontributor = async (e: React.FormEvent) => {
e.preventDefault();
if (!firstNameKontributor.trim() || !lastNameKontributor.trim()) {
MySwal.fire(
"Peringatan",
"Nama depan dan belakang wajib diisi",
"warning"
);
return;
}
if (!validateEmail(email)) {
MySwal.fire("Peringatan", "Email tidak valid", "warning");
return;
}
if (!validatePassword(kontributorPassword)) {
MySwal.fire("Peringatan", "Password minimal 8 karakter", "warning");
return;
}
const fullName = `${firstNameKontributor} ${lastNameKontributor}`;
const payload = {
address: "",
clientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead",
email,
fullName,
password: kontributorPassword,
phoneNumber: whatsappKontributor,
username: fullName.toLowerCase().replace(/\s+/g, "-"),
userLevelId: 1,
userRoleId: 5, // MISAL role kontributor
};
try {
setIsLoading(true);
const res = await createUser(payload);
if (res?.error) {
MySwal.fire("Gagal", res?.message || "Gagal mendaftar", "error");
} else {
MySwal.fire("Berhasil", "Akun kontributor berhasil dibuat", "success");
router.push("/auth");
}
} catch (err) {
MySwal.fire("Error", "Terjadi kesalahan server", "error");
} finally {
setIsLoading(false);
}
};
const handleCreateUserUmum = async (e: React.FormEvent) => {
e.preventDefault();

View File

@ -323,7 +323,7 @@ function Card({
</div>
<div className="py-[26px] px-4 space-y-2">
<div className="flex items-center gap-2 text-xs font-semibold flex-wrap">
<div className="flex justify-between items-center gap-2 text-xs font-semibold flex-row">
<span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
{item.clientName}
</span>

View File

@ -329,15 +329,7 @@ export default function MediaUpdate() {
}
};
function SafeImage({
src,
alt,
href,
}: {
src?: string;
alt?: string;
href: string;
}) {
function SafeImage({ src, alt }: { src?: string; alt?: string }) {
const [imgSrc, setImgSrc] = useState(src || DEFAULT_IMAGE);
useEffect(() => {
@ -345,15 +337,13 @@ export default function MediaUpdate() {
}, [src]);
return (
<Link href={href}>
<Image
src={imgSrc}
alt={alt || "Image"}
fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
onError={() => setImgSrc(DEFAULT_IMAGE)}
/>
</Link>
<Image
src={imgSrc}
alt={alt || "Image"}
fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
onError={() => setImgSrc(DEFAULT_IMAGE)}
/>
);
}
@ -504,7 +494,6 @@ export default function MediaUpdate() {
<SafeImage
src={item.smallThumbnailLink}
alt={item.title}
href={getLink(item)}
/>
{/* <Image
@ -519,9 +508,9 @@ export default function MediaUpdate() {
{/* Caption / info */}
<div className="p-3">
<div className="flex items-center gap-2 text-xs font-semibold flex-wrap mb-2">
<div className="flex items-center gap-2 text-xs font-semibold flex-row justify-between mb-2">
<span className="text-xs text-white px-2 py-0.5 rounded bg-emerald-600">
{item.clientName || "Tanpa Kategori"}
{item.clientName}
</span>
<span className="text-orange-600">
{item.categories

View File

@ -187,7 +187,7 @@ export default function Navbar() {
<div className="relative">
<button
onClick={() => setShowProfileMenu((prev) => !prev)}
className="flex items-center gap-2 border-2 py-1 px-3 rounded-lg hover:bg-gray-50"
className="flex items-center gap-2 border-2 py-1 px-3 rounded-lg hover:bg-gray-50 cursor-pointer"
>
<div className="w-9 h-9 rounded-full overflow-hidden border">
<Image