fix: all error in admin
This commit is contained in:
parent
e26bd85880
commit
46964678c2
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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[]) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue