This commit is contained in:
Sabda Yagra 2026-01-07 23:07:04 +07:00
commit 8b6d4ae00f
10 changed files with 228 additions and 85 deletions

View File

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

View File

@ -176,7 +176,7 @@ const usePendingApprovalColumns = () => {
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
size="icon" 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> <span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" /> <MoreVertical className="h-4 w-4 text-default-800" />
@ -187,7 +187,7 @@ const usePendingApprovalColumns = () => {
href={`/admin/content/image/detail/${row.original.id}`} href={`/admin/content/image/detail/${row.original.id}`}
className="hover:text-black" 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" /> <Eye className="w-4 h-4 me-1.5" />
View View
</DropdownMenuItem> </DropdownMenuItem>

View File

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

View File

@ -27,7 +27,7 @@ const ReactTableImagePage = () => {
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Link href={"/admin/content/image/create"}> <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" /> <UploadIcon size={18} className="mr-2" />
Create Image Create Image
</Button> </Button>

View File

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

View File

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

View File

@ -94,7 +94,7 @@ export default function SignUp() {
// Kontributor (sementara ikut umum) // Kontributor (sementara ikut umum)
if (role === "kontributor") { if (role === "kontributor") {
await handleCreateUserUmum(e); await handleCreateUserKontributor(e);
return; return;
} }
@ -134,6 +134,59 @@ export default function SignUp() {
return password.length >= 8; 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) => { const handleCreateUserUmum = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();

View File

@ -323,7 +323,7 @@ function Card({
</div> </div>
<div className="py-[26px] px-4 space-y-2"> <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"> <span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
{item.clientName} {item.clientName}
</span> </span>

View File

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

View File

@ -187,7 +187,7 @@ export default function Navbar() {
<div className="relative"> <div className="relative">
<button <button
onClick={() => setShowProfileMenu((prev) => !prev)} 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"> <div className="w-9 h-9 rounded-full overflow-hidden border">
<Image <Image