fix: fixing bugs in userlevels

This commit is contained in:
Sabda Yagra 2026-01-22 09:20:17 +07:00
parent b6bde7722e
commit fc1d4a6b9a
23 changed files with 1859 additions and 784 deletions

View File

@ -17,6 +17,7 @@ import { deleteArticle, deleteMedia } from "@/service/content/content";
import { error } from "@/lib/swal";
import Swal from "sweetalert2";
import Link from "next/link";
import { AccessGuard } from "@/components/access-guard";
const useTableColumns = () => {
const MySwal = withReactContent(Swal);
@ -137,57 +138,119 @@ const useTableColumns = () => {
);
},
},
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
const {
statusId,
statusName,
isPublish,
reviewedAtLevel = "",
creatorGroupLevelId,
needApprovalFromLevel,
} = row.original;
const userLevelId = Number(getCookiesDecrypt("ulie"));
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
const isCreator = Number(creatorGroupLevelId) === 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";
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-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 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 =
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
className={cn(
"rounded-full px-5 w-full whitespace-nowrap",
statusStyles
)}
>
{(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>
<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>
);
},
},
// {
// accessorKey: "statusName",
// header: "Status",
// cell: ({ row }) => {
// const statusColors: Record<string, string> = {
// diterima: "bg-green-100 text-green-600",
// "menunggu review": "bg-orange-100 text-orange-600",
// };
// 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 =
// 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
// className={cn(
// "rounded-full px-5 w-full whitespace-nowrap",
// statusStyles
// )}
// >
// {(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>
// );
// },
// },
{
id: "actions",
accessorKey: "action",
@ -241,7 +304,7 @@ const useTableColumns = () => {
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(
Number(userLevelId) == 216 && Number(roleId) == 3
Number(userLevelId) == 216 && Number(roleId) == 3,
);
}
}, [userLevelId, roleId]);
@ -258,12 +321,15 @@ const useTableColumns = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/content/audio/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
<AccessGuard action="view">
<Link href={`/admin/content/audio/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
</AccessGuard>
{/* <Link
href={`/admin/content/audio/update/${row.original.id}`}
>
@ -272,22 +338,31 @@ const useTableColumns = () => {
Edit
</DropdownMenuItem>
</Link> */}
{(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && (
{/* {(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && ( */}
<AccessGuard action="edit">
<Link href={`/admin/content/audio/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<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"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
</AccessGuard>
{/* )} */}
<AccessGuard action="delete">
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
</AccessGuard>
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}

View File

@ -5,8 +5,12 @@ import AudioTabs from "./components/audio-tabs";
import { UploadIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { usePermission } from "@/components/context/permission-context";
import { AccessGuard } from "@/components/access-guard";
const ReactTableAudioPage = () => {
const { can } = usePermission();
return (
<div className="min-h-screen bg-gray-50">
<SiteBreadcrumb />
@ -21,18 +25,27 @@ const ReactTableAudioPage = () => {
<UploadIcon className="w-4 h-4 text-blue-600" />
</div>
<div>
<h1 className="text-xl font-semibold text-gray-900">Audio Management</h1>
<p className="text-sm text-gray-500">Manage your submitted audio files and pending approvals</p>
<h1 className="text-xl font-semibold text-gray-900">
Audio Management
</h1>
<p className="text-sm text-gray-500">
Manage your submitted audio files and pending approvals
</p>
</div>
</div>
<div className="flex-none">
<Link href={"/admin/content/audio/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<UploadIcon size={18} className="mr-2" />
Create Audio
</Button>
</Link>
</div>
<AccessGuard action="create">
<div className="flex-none">
<Link href={"/admin/content/audio/create"}>
<Button
color="primary"
className="text-white shadow-sm hover:shadow-md transition-shadow"
>
<UploadIcon size={18} className="mr-2" />
Create Audio
</Button>
</Link>
</div>
</AccessGuard>
</div>
</CardTitle>
</CardHeader>
@ -46,4 +59,4 @@ const ReactTableAudioPage = () => {
);
};
export default ReactTableAudioPage;
export default ReactTableAudioPage;

View File

@ -19,6 +19,7 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import Link from "next/link";
import { deleteArticle } from "@/service/content/content";
import { AccessGuard } from "@/components/access-guard";
const useTableColumns = () => {
const MySwal = withReactContent(Swal);
@ -183,7 +184,6 @@ const useTableColumns = () => {
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
const isCreator = Number(creatorGroupLevelId) === userLevelId;
if (isPublish) {
return (
<div className="flex items-center justify-center w-full h-full">
@ -193,7 +193,7 @@ const useTableColumns = () => {
</div>
);
}
let label = statusName || "Menunggu Review";
if (statusId === 2 && !userHasReviewed && !isCreator) {
@ -217,7 +217,7 @@ const useTableColumns = () => {
<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
colors[label] || colors.default,
)}
>
{label}
@ -281,7 +281,7 @@ const useTableColumns = () => {
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(
Number(userLevelId) === 216 && Number(roleId) === 3
Number(userLevelId) === 216 && Number(roleId) === 3,
);
}
}, [userLevelId, roleId]);
@ -298,30 +298,38 @@ const useTableColumns = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0 hover:text-black" align="end">
<Link
href={`/admin/content/image/detail/${row.original.id}`}
className="hover:text-black"
>
<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>
<AccessGuard action="view">
<Link
href={`/admin/content/image/detail/${row.original.id}`}
className="hover:text-black"
>
<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>
</AccessGuard>
{/* {(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 cursor-pointer hover:bg-slate-200">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
<AccessGuard action="edit">
<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>
</AccessGuard>
{/* )} */}
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem>
<AccessGuard action="delete">
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem>
</AccessGuard>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -5,8 +5,12 @@ import ImageTabs from "./components/image-tabs";
import { UploadIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { usePermission } from "@/components/context/permission-context";
import { AccessGuard } from "@/components/access-guard";
const ReactTableImagePage = () => {
const { can } = usePermission();
return (
<div className="min-h-screen bg-gray-50">
<SiteBreadcrumb />
@ -21,24 +25,33 @@ const ReactTableImagePage = () => {
<UploadIcon className="w-4 h-4 text-blue-600" />
</div>
<div>
<h1 className="text-xl font-semibold text-gray-900">Image Management</h1>
<p className="text-sm text-gray-500">Manage your submitted images and pending approvals</p>
<h1 className="text-xl font-semibold text-gray-900">
Image Management
</h1>
<p className="text-sm text-gray-500">
Manage your submitted images and pending approvals
</p>
</div>
</div>
<div className="flex-none">
<Link href={"/admin/content/image/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow cursor-pointer">
<UploadIcon size={18} className="mr-2" />
Create Image
</Button>
</Link>
{/* <Link href={"/contributor/content/image/createAi"}>
<AccessGuard action="create">
<div className="flex-none">
<Link href={"/admin/content/image/create"}>
<Button
color="primary"
className="text-white shadow-sm hover:shadow-md transition-shadow cursor-pointer"
>
<UploadIcon size={18} className="mr-2" />
Create Image
</Button>
</Link>
{/* <Link href={"/contributor/content/image/createAi"}>
<Button color="primary" className="text-white ml-3">
<UploadIcon />
Unggah Foto Dengan AI
</Button>
</Link> */}
</div>
</div>
</AccessGuard>
</div>
</CardTitle>
</CardHeader>

View File

@ -17,6 +17,7 @@ import { deleteArticle, deleteMedia } from "@/service/content/content";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import Link from "next/link";
import { AccessGuard } from "@/components/access-guard";
const useTableColumns = () => {
const MySwal = withReactContent(Swal);
@ -142,50 +143,59 @@ const useTableColumns = () => {
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
const {
statusId,
statusName,
isPublish,
reviewedAtLevel = "",
creatorGroupLevelId,
needApprovalFromLevel,
} = row.original;
const userLevelId = Number(getCookiesDecrypt("ulie"));
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
const isCreator = Number(creatorGroupLevelId) === 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";
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-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 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 =
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
className={cn(
"rounded-full px-5 w-full whitespace-nowrap",
statusStyles
)}
>
{(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>
<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>
);
},
},
@ -242,7 +252,7 @@ const useTableColumns = () => {
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(
Number(userLevelId) == 216 && Number(roleId) == 3
Number(userLevelId) == 216 && Number(roleId) == 3,
);
}
}, [userLevelId, roleId]);
@ -259,28 +269,37 @@ const useTableColumns = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/content/text/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
{(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && (
<Link href={`/admin/content/text/update/${row.original.id}`}>
<AccessGuard action="view">
<Link href={`/admin/content/text/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
</AccessGuard>
{/* {(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && ( */}
<AccessGuard action="edit">
<Link href={`/admin/content/text/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<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"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
</AccessGuard>
{/* )} */}
<AccessGuard action="delete">
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
</AccessGuard>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -5,8 +5,12 @@ import DocumentTabs from "./components/document-tabs";
import { UploadIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { usePermission } from "@/components/context/permission-context";
import { AccessGuard } from "@/components/access-guard";
const ReactTableDocumentPage = () => {
const { can } = usePermission();
return (
<div className="min-h-screen bg-gray-50">
{/* <SiteBreadcrumb /> */}
@ -21,18 +25,28 @@ const ReactTableDocumentPage = () => {
<UploadIcon className="w-4 h-4 text-blue-600" />
</div>
<div>
<h1 className="text-xl font-semibold text-gray-900">Document Management</h1>
<p className="text-sm text-gray-500">Manage your submitted documents and pending approvals</p>
<h1 className="text-xl font-semibold text-gray-900">
Document Management
</h1>
<p className="text-sm text-gray-500">
Manage your submitted documents and pending approvals
</p>
</div>
</div>
<div className="flex-none">
<Link href={"/admin/content/text/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<UploadIcon size={18} className="mr-2" />
Create Document
</Button>
</Link>
</div>
<AccessGuard action="create">
{" "}
<div className="flex-none">
<Link href={"/admin/content/text/create"}>
<Button
color="primary"
className="text-white shadow-sm hover:shadow-md transition-shadow"
>
<UploadIcon size={18} className="mr-2" />
Create Document
</Button>
</Link>
</div>
</AccessGuard>
</div>
</CardTitle>
</CardHeader>

View File

@ -18,6 +18,7 @@ import { error } from "@/lib/swal";
import Link from "next/link";
import { deleteMedia } from "@/service/content";
import { deleteArticle } from "@/service/content/content";
import { AccessGuard } from "@/components/access-guard";
const useTableColumns = () => {
const MySwal = withReactContent(Swal);
@ -194,45 +195,59 @@ 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 {
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>
);
}
const label =
isWaitingForReview || isApprovalNeeded
? "Menunggu Review"
: statusId === 2
? "Diterima"
: row.original?.statusName;
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> = {
"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>
);
},
},
@ -289,7 +304,7 @@ const useTableColumns = () => {
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(
Number(userLevelId) == 216 && Number(roleId) == 3
Number(userLevelId) == 216 && Number(roleId) == 3,
);
}
}, [userLevelId, roleId]);
@ -306,12 +321,15 @@ const useTableColumns = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/content/video/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
<AccessGuard action="view">
<Link href={`/admin/content/video/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
</AccessGuard>
{/* <Link
href={`/contributor/content/video/update/${row.original.id}`}
>
@ -322,20 +340,27 @@ const useTableColumns = () => {
</Link> */}
{/* {(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && ( */}
<Link href={`/admin/content/video/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
<AccessGuard action="edit">
<Link href={`/admin/content/video/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
</AccessGuard>
{/* )} */}
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
<AccessGuard action="delete">
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
Delete
</DropdownMenuItem>
</AccessGuard>
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
<DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}

View File

@ -72,18 +72,14 @@ const TableVideo = () => {
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),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [categories, setCategories] = React.useState<any[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[]
@ -95,7 +91,6 @@ const TableVideo = () => {
const [filterByCreator, setFilterByCreator] = React.useState("");
const [filterBySource, setFilterBySource] = React.useState("");
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
const roleId = getCookiesDecrypt("urie");
const columns = useTableColumns();
const table = useReactTable({

View File

@ -5,8 +5,12 @@ import AudioVisualTabs from "./components/audio-visual-tabs";
import { UploadIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { usePermission } from "@/components/context/permission-context";
import { AccessGuard } from "@/components/access-guard";
const ReactTableAudioVisualPage = () => {
const { can } = usePermission();
return (
<div className="min-h-screen bg-gray-50">
<SiteBreadcrumb />
@ -17,22 +21,32 @@ const ReactTableAudioVisualPage = () => {
<CardTitle>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<div className="w-8 h-8 bg-black rounded-lg flex items-center justify-center">
<UploadIcon className="w-4 h-4 text-blue-600" />
</div>
<div>
<h1 className="text-xl font-semibold text-gray-900">Audio-Visual Management</h1>
<p className="text-sm text-gray-500">Manage your submitted audio-visual files and pending approvals</p>
<h1 className="text-xl font-semibold text-gray-900">
Audio-Visual Management
</h1>
<p className="text-sm text-gray-500">
Manage your submitted audio-visual files and pending
approvals
</p>
</div>
</div>
<div className="flex-none">
<Link href={"/admin/content/video/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<UploadIcon size={18} className="mr-2" />
Create Audio-Visual
</Button>
</Link>
</div>
<AccessGuard action="create">
<div className="flex-none">
<Link href={"/admin/content/video/create"}>
<Button
color="primary"
className="text-white shadow-sm hover:shadow-md transition-shadow"
>
<UploadIcon size={18} className="mr-2" />
Create Audio-Visual
</Button>
</Link>
</div>
</AccessGuard>
</div>
</CardTitle>
</CardHeader>
@ -46,4 +60,4 @@ const ReactTableAudioVisualPage = () => {
);
};
export default ReactTableAudioVisualPage;
export default ReactTableAudioVisualPage;

View File

@ -32,7 +32,7 @@ export default function ManagementUser() {
return (
<div>
<SiteBreadcrumb />
<section className="flex flex-col gap-2 bg-slate-50 dark:bg-black rounded-lg p-3 mt-5 border">
<section className="flex flex-col gap-2 bg-white dark:bg-slate-200 rounded-lg p-3 mt-5 border">
<div className="flex justify-between py-3">
<p className="text-lg">
Data User

View File

@ -6,21 +6,52 @@ import ThemeCustomize from "@/components/partials/customizer";
import DashCodeHeader from "@/components/partials/header";
import MountedProvider from "@/providers/mounted.provider";
import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
import { PermissionProvider } from "@/components/context/permission-context";
const layout = async ({ children }: { children: React.ReactNode }) => {
return (
<MountedProvider isProtected={true}>
<LayoutProvider>
<WorkflowModalProvider>
<ThemeCustomize />
<DashCodeHeader />
<DashCodeSidebar />
<LayoutContentProvider>{children}</LayoutContentProvider>
<DashCodeFooter />
</WorkflowModalProvider>
</LayoutProvider>
{/* 🔐 Permission hanya untuk admin */}
<PermissionProvider>
<LayoutProvider>
<WorkflowModalProvider>
<ThemeCustomize />
<DashCodeHeader />
<DashCodeSidebar />
<LayoutContentProvider>{children}</LayoutContentProvider>
<DashCodeFooter />
</WorkflowModalProvider>
</LayoutProvider>
</PermissionProvider>
</MountedProvider>
);
};
export default layout;
// import LayoutProvider from "@/providers/layout.provider";
// import LayoutContentProvider from "@/providers/content.provider";
// import DashCodeSidebar from "@/components/partials/sidebar";
// import DashCodeFooter from "@/components/partials/footer";
// import ThemeCustomize from "@/components/partials/customizer";
// import DashCodeHeader from "@/components/partials/header";
// import MountedProvider from "@/providers/mounted.provider";
// import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
// const layout = async ({ children }: { children: React.ReactNode }) => {
// return (
// <MountedProvider isProtected={true}>
// <LayoutProvider>
// <WorkflowModalProvider>
// <ThemeCustomize />
// <DashCodeHeader />
// <DashCodeSidebar />
// <LayoutContentProvider>{children}</LayoutContentProvider>
// <DashCodeFooter />
// </WorkflowModalProvider>
// </LayoutProvider>
// </MountedProvider>
// );
// };
// export default layout;

View File

@ -0,0 +1,31 @@
import { usePermission } from "./context/permission-context";
export function AccessGuard({
module,
action,
children,
fallback = null,
}: {
module?: string;
action?: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}) {
const { canModule, canAction, can, loading } = usePermission();
if (loading) return null;
// ❗ WAJIB ADA RULE
if (!module && !action) {
if (process.env.NODE_ENV === "development") {
console.warn("AccessGuard requires module and/or action");
}
return fallback;
}
if (module && action && !can(module, action)) return fallback;
if (module && !action && !canModule(module)) return fallback;
if (!module && action && !canAction(action)) return fallback;
return <>{children}</>;
}

View File

@ -0,0 +1,116 @@
"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
import { getCookiesDecrypt } from "@/lib/utils";
import { getUserLevelModuleAccessesByUserLevelId } from "@/service/user-level-module-accesses";
import { getUserLevelMenuActionAccesses } from "@/service/user-level-menu-action-accesses";
import { getUserInfo } from "@/service/user";
type ModuleAccessMap = Record<string, boolean>;
type ActionAccessMap = Record<string, string[]>;
interface PermissionContextType {
canModule: (moduleCode: string) => boolean;
canAction: (actionCode: string) => boolean;
can: (moduleCode: string, actionCode: string) => boolean;
loading: boolean;
}
const PermissionContext = createContext<PermissionContextType | null>(null);
export const PermissionProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [moduleAccess, setModuleAccess] = useState<ModuleAccessMap>({});
const [actionCodes, setActionCodes] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadPermissions = async () => {
try {
// ✅ SUMBER KEBENARAN: API USER INFO
const userRes = await getUserInfo();
const userLevelId = userRes?.data?.data?.userLevelId;
console.log("USER LEVEL ID FROM API:", userLevelId);
if (!userLevelId) {
console.error("userLevelId not found from users/info");
return;
}
const [moduleRes, actionRes] = await Promise.all([
getUserLevelModuleAccessesByUserLevelId(userLevelId),
getUserLevelMenuActionAccesses({
userLevelId,
canAccess: true,
limit: 10000,
}),
]);
console.log("ACTION ACCESS RAW:", actionRes?.data?.data);
// 🔹 MODULE ACCESS
// MODULE ACCESS
const moduleMap: Record<string, boolean> = {};
moduleRes?.data?.data?.forEach((item: any) => {
if (
item.module?.code &&
item.canAccess === true &&
item.isActive !== false
) {
moduleMap[item.module.code] = true;
}
});
// ACTION ACCESS
const actions =
actionRes?.data?.data
?.filter(
(item: any) =>
Number(item.userLevelId) === Number(userLevelId) &&
item.canAccess === true &&
item.isActive === true,
)
.map((item: any) => item.actionCode) ?? [];
setModuleAccess(moduleMap);
setActionCodes(actions);
} catch (error) {
console.error("Failed to load permissions", error);
} finally {
setLoading(false);
}
};
loadPermissions();
}, []);
const canModule = (moduleCode: string) => moduleAccess[moduleCode] === true;
const canAction = (actionCode: string) => actionCodes.includes(actionCode);
/**
* FINAL GUARD
* - harus punya module
* - harus punya action
*/
const can = (moduleCode: any, actionCode: any) =>
canModule(moduleCode) && canAction(actionCode);
return (
<PermissionContext.Provider value={{ canModule, canAction, can, loading }}>
{children}
</PermissionContext.Provider>
);
};
export const usePermission = () => {
const ctx = useContext(PermissionContext);
if (!ctx) {
throw new Error("usePermission must be used inside PermissionProvider");
}
return ctx;
};

File diff suppressed because it is too large Load Diff

View File

@ -48,6 +48,7 @@ import dynamic from "next/dynamic";
import { formatDateToIndonesian } from "@/utils/globals";
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
import { listArticleCategories } from "@/service/content";
import { AccessGuard } from "@/components/access-guard";
const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -198,7 +199,7 @@ export default function FormVideoDetail() {
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id],
);
};
@ -234,6 +235,15 @@ export default function FormVideoDetail() {
if (!detail) return <div className="p-10 text-gray-500">Memuat data...</div>;
const isPending = Number(detail?.statusId) === 1;
const isApproved = Number(detail?.statusId) === 2;
const isRejected = Number(detail?.statusId) === 4;
const isCreator =
Number(detail?.createdById || detail?.uploadedById) === Number(userId);
const hasApproval = approval != null;
return (
<form>
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
@ -325,7 +335,7 @@ export default function FormVideoDetail() {
</div>
<div className="mt-3 px-3 space-y-2">
<Label>Preview</Label>
<Label>Thumbnail</Label>
<Card className="mt-2 w-fit">
<img
src={detail.thumbnailUrl || detail.thumbnailLink}
@ -387,7 +397,46 @@ export default function FormVideoDetail() {
</div>
)}
{(Number(detail.needApprovalFromLevel || 0) ==
{/* {isPending &&
(Number(detail.needApprovalFromLevel || 0) ===
Number(userLevelId) ||
detail.isPublish === false) &&
Number(detail.uploadedById || detail.createdById) !==
Number(userId) ? ( */}
{isPending && (
<AccessGuard action="approve">
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" /> Reject
</Button>
</div>
</AccessGuard>
)}
{/* ) : null} */}
{/* {(Number(detail.needApprovalFromLevel || 0) ==
Number(userLevelId) ||
(detail.isPublish === false && detail.statusId == 1)) &&
Number(detail.uploadedById || detail.createdById) !=
@ -415,7 +464,7 @@ export default function FormVideoDetail() {
<Icon icon="fa:times" className="mr-3" /> Reject
</Button>
</div>
) : null}
) : null} */}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="max-h-[600px] overflow-y-auto">

View File

@ -66,6 +66,7 @@ import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
import { useDropzone } from "react-dropzone";
import AudioPlayer from "@/components/audio-player";
import { listArticleCategories } from "@/service/content";
import { AccessGuard } from "@/components/access-guard";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -112,7 +113,7 @@ const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormAudioDetail() {
@ -219,7 +220,7 @@ export default function FormAudioDetail() {
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
);
};
@ -249,9 +250,7 @@ export default function FormAudioDetail() {
if (id) {
const response = await getArticleDetail(Number(id));
const details = response?.data?.data;
console.log("detail", details);
setFiles(details?.files);
console.log("ISI FILES:", details?.files);
setSelectedCategory(String(details.categories[0].id));
setDetail(details);
@ -268,12 +267,20 @@ export default function FormAudioDetail() {
if (details?.publishedForObject) {
const publisherIds = details?.publishedForObject.map(
(obj: any) => obj.id
(obj: any) => obj.id,
);
setSelectedPublishers(publisherIds);
}
setSelectedTarget(String(details.category.id));
const categoryId =
details?.category?.id ||
details?.categoryId ||
details?.categories?.[0]?.id;
if (categoryId) {
setSelectedTarget(String(categoryId));
setSelectedCategory(String(categoryId));
}
const filesData = details?.files || [];
// const audioFiles = filesData.filter(
@ -382,7 +389,7 @@ export default function FormAudioDetail() {
const setupPlacement = (
index: number,
placement: string,
checked: boolean
checked: boolean,
) => {
let temp = [...filePlacements];
if (checked) {
@ -416,7 +423,7 @@ export default function FormAudioDetail() {
type: string,
url: string,
names: string,
format: string
format: string,
) => {
console.log("Test 3 :", type, url, names, format);
setMain({
@ -448,6 +455,8 @@ export default function FormAudioDetail() {
});
};
const isPending = Number(detail?.statusId) === 1;
return (
<form>
{detail !== undefined ? (
@ -647,7 +656,7 @@ export default function FormAudioDetail() {
id="terms"
value="all"
checked={filePlacements[index]?.includes(
"all"
"all",
)}
onCheckedChange={(e) =>
setupPlacement(index, "all", Boolean(e))
@ -664,7 +673,7 @@ export default function FormAudioDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"mabes"
"mabes",
)}
onCheckedChange={(e) =>
setupPlacement(index, "mabes", Boolean(e))
@ -681,7 +690,7 @@ export default function FormAudioDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"polda"
"polda",
)}
onCheckedChange={(e) =>
setupPlacement(index, "polda", Boolean(e))
@ -699,13 +708,13 @@ export default function FormAudioDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"international"
"international",
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"international",
Boolean(e)
Boolean(e),
)
}
/>
@ -821,30 +830,34 @@ export default function FormAudioDetail() {
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" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
Reject
</Button>
</div>
{isPending && (
<AccessGuard action="approve">
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
Reject
</Button>
</div>
</AccessGuard>
)}
{/* )
) : (
""

View File

@ -65,6 +65,7 @@ import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
import FileTextPreview from "../file-preview-text";
import FileTextThumbnail from "../file-text-thumbnail";
import { listArticleCategories } from "@/service/content";
import { AccessGuard } from "@/components/access-guard";
type Option = {
id: string;
@ -116,7 +117,7 @@ const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormTeksDetail() {
@ -195,7 +196,7 @@ export default function FormTeksDetail() {
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
);
};
@ -253,7 +254,7 @@ export default function FormTeksDetail() {
if (details?.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
(obj: any) => obj.id,
);
setSelectedPublishers(publisherIds);
}
@ -359,7 +360,7 @@ export default function FormTeksDetail() {
const setupPlacement = (
index: number,
placement: string,
checked: boolean
checked: boolean,
) => {
let temp = [...filePlacements];
if (checked) {
@ -400,7 +401,7 @@ export default function FormTeksDetail() {
type: string,
url: string,
names: string,
format: string
format: string,
) => {
console.log("Test 3 :", type, url, names, format);
setMain({
@ -424,6 +425,8 @@ export default function FormTeksDetail() {
});
};
const isPending = Number(detail?.statusId) === 1;
return (
<form>
{detail !== undefined ? (
@ -528,10 +531,10 @@ export default function FormTeksDetail() {
</Card>
<div className="w-full lg:w-4/12 m-2">
<Card className="pb-3">
<div className="px-3 py-3">
<Label>Creator</Label>
<Input value={detail.createdByName} disabled />
</div>
<div className="px-3 py-3">
<Label>Creator</Label>
<Input value={detail.createdByName} disabled />
</div>
{/* <div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label>
<Card className="mt-2">
@ -647,7 +650,7 @@ export default function FormTeksDetail() {
id="terms"
value="all"
checked={filePlacements[index]?.includes(
"all"
"all",
)}
onCheckedChange={(e) =>
setupPlacement(index, "all", Boolean(e))
@ -664,7 +667,7 @@ export default function FormTeksDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"mabes"
"mabes",
)}
onCheckedChange={(e) =>
setupPlacement(index, "mabes", Boolean(e))
@ -681,7 +684,7 @@ export default function FormTeksDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"polda"
"polda",
)}
onCheckedChange={(e) =>
setupPlacement(index, "polda", Boolean(e))
@ -699,13 +702,13 @@ export default function FormTeksDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"international"
"international",
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"international",
Boolean(e)
Boolean(e),
)
}
/>
@ -817,10 +820,17 @@ export default function FormTeksDetail() {
</DialogContent>
</Dialog>
</Card>
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
Number(detail?.uploadedById) == Number(userId) ? (
{/* {isPending &&
(Number(detail?.needApprovalFromLevel || 0) ===
Number(userLevelId) ||
(detail?.isInternationalMedia === true &&
detail?.isForwardFromNational === true)) ? (
Number(detail?.createdById || detail?.uploadedById) ==
Number(userId) ? (
""
) : ( */}
{isPending && (
<AccessGuard action="approve">
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
@ -829,6 +839,7 @@ export default function FormTeksDetail() {
>
<Icon icon="fa:check" className="mr-3" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
@ -836,6 +847,7 @@ export default function FormTeksDetail() {
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
@ -845,7 +857,9 @@ export default function FormTeksDetail() {
Reject
</Button>
</div>
{/* )
</AccessGuard>
)}
{/* )
) : (
""
)} */}

View File

@ -60,6 +60,7 @@ import {
getDataApprovalByMediaUpload,
} from "@/service/curated-content/curated-content";
import { UnitMapping } from "../unit-mapping";
import { AccessGuard } from "@/components/access-guard";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -161,7 +162,7 @@ const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormImageDetail() {
@ -171,10 +172,8 @@ export default function FormImageDetail() {
const userLevelId = getCookiesDecrypt("ulie");
const userLevelName = Cookies.get("state");
const roleId = getCookiesDecrypt("urie");
console.log("LALALALA", userLevelName);
const [modalOpen, setModalOpen] = useState(false);
const { id } = useParams() as { id: string };
console.log("IDIDIDIDI", id);
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
@ -233,7 +232,7 @@ export default function FormImageDetail() {
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
);
};
@ -255,7 +254,7 @@ export default function FormImageDetail() {
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory?.find((o) =>
o.name.toLowerCase().includes("pers rilis")
o.name.toLowerCase().includes("pers rilis"),
);
if (findCategory) {
@ -365,7 +364,7 @@ export default function FormImageDetail() {
setSelectedTarget(String(mappedDetail.categoryId));
const fileUrls = (mappedFiles || []).map(
(file) => file.thumbnailFileUrl || file.url || "default-image.jpg"
(file) => file.thumbnailFileUrl || file.url || "default-image.jpg",
);
setDetailThumb(fileUrls);
@ -478,7 +477,7 @@ export default function FormImageDetail() {
const setupPlacement = (
index: number,
placement: string,
checked: boolean
checked: boolean,
) => {
let temp = [...filePlacements];
if (checked) {
@ -519,7 +518,7 @@ export default function FormImageDetail() {
type: string,
url: string,
names: string,
format: string
format: string,
) => {
console.log("Test 3 :", type, url, names, format);
setMain({
@ -566,6 +565,15 @@ export default function FormImageDetail() {
console.log("portrai", portraitMap);
}, [portraitMap]);
const isPending = Number(detail?.statusId) === 1;
const isApproved = Number(detail?.statusId) === 2;
const isRejected = Number(detail?.statusId) === 4;
const isCreator =
Number(detail?.createdById || detail?.uploadedById) === Number(userId);
const hasApproval = approval != null;
return (
<form>
{detail !== undefined ? (
@ -615,14 +623,14 @@ export default function FormImageDetail() {
!categories?.find(
(cat) =>
String(cat.id) ===
String(detail.categoryId || detail?.category?.id)
String(detail.categoryId || detail?.category?.id),
) && (
<SelectItem
key={String(
detail.categoryId || detail?.category?.id
detail.categoryId || detail?.category?.id,
)}
value={String(
detail.categoryId || detail?.category?.id
detail.categoryId || detail?.category?.id,
)}
>
{detail.categoryName || detail?.category?.name}
@ -915,7 +923,7 @@ export default function FormImageDetail() {
id="terms"
value="all"
checked={filePlacements[index]?.includes(
"all"
"all",
)}
onCheckedChange={(e) =>
setupPlacement(index, "all", Boolean(e))
@ -932,13 +940,13 @@ export default function FormImageDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"mabes"
"mabes",
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"mabes",
Boolean(e)
Boolean(e),
)
}
/>
@ -953,13 +961,13 @@ export default function FormImageDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"polda"
"polda",
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"polda",
Boolean(e)
Boolean(e),
)
}
/>
@ -984,13 +992,13 @@ export default function FormImageDetail() {
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"international"
"international",
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"international",
Boolean(e)
Boolean(e),
)
}
/>
@ -1111,15 +1119,22 @@ export default function FormImageDetail() {
</DialogContent>
</Dialog>
</Card>
{Number(detail?.needApprovalFromLevel || 0) ==
{/* {Number(detail?.needApprovalFromLevel || 0) ==
Number(userLevelId) ||
(detail?.isInternationalMedia == true &&
detail?.isForwardFromNational == true &&
Number(detail?.statusId) == 1) ? (
Number(detail?.statusId) == 1) ? ( */}
{/* {isPending &&
(Number(detail?.needApprovalFromLevel || 0) ===
Number(userLevelId) ||
(detail?.isInternationalMedia === true &&
detail?.isForwardFromNational === true)) ? (
Number(detail?.createdById || detail?.uploadedById) ==
Number(userId) ? (
""
) : (
) : ( */}
{isPending && (
<AccessGuard action="approve">
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
@ -1145,10 +1160,12 @@ export default function FormImageDetail() {
Reject
</Button>
</div>
)
</AccessGuard>
)}
{/* )
) : (
""
)}
)} */}
</div>
</div>
) : (

View File

@ -56,6 +56,7 @@ export default function SignUp() {
const [whatsapp, setWhatsapp] = useState("");
const [namaTenant, setNamaTenant] = useState("");
const [tenantPassword, setTenantPassword] = useState("");
const [tenantUsername, setTenantUsername] = useState("");
const [confirmTenantPassword, setConfirmTenantPassword] = useState("");
const [firstNameKontributor, setFirstNameKontributor] = useState("");
const [lastNameKontributor, setLastNameKontributor] = useState("");
@ -119,7 +120,11 @@ export default function SignUp() {
if (value && nextInput) nextInput.focus();
};
// Form validation functions
const validateName = (value: string) => {
const nameRegex = /^[A-Za-z\s]+$/;
return nameRegex.test(value.trim());
};
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
@ -134,6 +139,11 @@ export default function SignUp() {
return password.length >= 8;
};
const validateUsername = (username: string) => {
const usernameRegex = /^[a-z0-9_-]+$/;
return usernameRegex.test(username);
};
const handleCreateUserKontributor = async (e: React.FormEvent) => {
e.preventDefault();
@ -141,7 +151,7 @@ export default function SignUp() {
MySwal.fire(
"Peringatan",
"Nama depan dan belakang wajib diisi",
"warning"
"warning",
);
return;
}
@ -260,12 +270,29 @@ export default function SignUp() {
const validateTenantForm = () => {
const errors: any = {};
// if (!firstName.trim()) {
// errors.firstName = "First name is required";
// }
if (!firstName.trim()) {
errors.firstName = "First name is required";
errors.firstName = "First name wajib diisi";
} else if (!validateName(firstName)) {
errors.firstName = "First name hanya boleh huruf dan spasi";
}
// if (!lastName.trim()) {
// errors.lastName = "Last name is required";
// }
if (!lastName.trim()) {
errors.lastName = "Last name is required";
errors.lastName = "Last name wajib diisi";
} else if (!validateName(lastName)) {
errors.lastName = "Last name hanya boleh huruf dan spasi";
}
if (!tenantUsername.trim()) {
errors.tenantUsername = "Username wajib diisi";
} else if (!validateUsername(tenantUsername)) {
errors.tenantUsername =
"Username hanya boleh huruf kecil, angka, tanpa spasi";
}
if (!email.trim()) {
@ -303,9 +330,7 @@ export default function SignUp() {
const handleTenantRegistration = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateTenantForm()) {
return;
}
if (!validateTenantForm()) return;
setIsLoading(true);
setFormErrors({});
@ -314,43 +339,75 @@ export default function SignUp() {
const registrationData = {
adminUser: {
address: "Jakarta",
email: email,
fullname: `${firstName} ${lastName}`,
email: email.trim(),
fullname: `${firstName.trim()} ${lastName.trim()}`,
password: tenantPassword,
phoneNumber: whatsapp,
username: `${firstName}-${lastName}`,
phoneNumber: whatsapp.trim(),
username: tenantUsername.trim(),
},
client: {
clientType: "sub_client",
name: namaTenant,
name: namaTenant.trim(),
parentClientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead",
},
};
const response = await registerTenant(registrationData);
if (response.error) {
console.log("📦 registerTenant response:", response);
/**
* KUNCI UTAMA
* Backend gagal error === true
*/
if (response.error === true) {
const backendMessage =
typeof response.message === "string"
? response.message
: JSON.stringify(response.message);
// 🔴 Duplicate tenant / slug
if (
backendMessage.includes("clients_slug_key") ||
backendMessage.toLowerCase().includes("duplicate")
) {
MySwal.fire({
title: "Tenant Sudah Terdaftar",
text: "Nama tenant sudah digunakan. Silakan gunakan nama tenant lain.",
icon: "warning",
confirmButtonText: "OK",
});
return;
}
// 🔴 General error
MySwal.fire({
title: "Registration Failed",
text: response.message || "An error occurred during registration",
title: "Registrasi Gagal",
text: backendMessage || "Tenant gagal dibuat",
icon: "error",
confirmButtonText: "OK",
});
} else {
MySwal.fire({
title: "Registration Successful",
text: "Your tenant account has been created successfully!",
icon: "success",
confirmButtonText: "OK",
}).then(() => {
router.push("/auth");
});
return;
}
} catch (error) {
console.error("Registration error:", error);
/**
* SUCCESS
* HANYA jika error === false
*/
await MySwal.fire({
title: "Registrasi Berhasil 🎉",
text: "Akun tenant berhasil dibuat. Silakan login.",
icon: "success",
confirmButtonText: "OK",
});
router.push("/auth");
} catch (err) {
console.error("❌ Tenant registration error:", err);
MySwal.fire({
title: "Registration Failed",
text: "An unexpected error occurred. Please try again.",
title: "Terjadi Kesalahan",
text: "Terjadi kesalahan server. Silakan coba lagi.",
icon: "error",
confirmButtonText: "OK",
});
@ -359,6 +416,66 @@ export default function SignUp() {
}
};
// const handleTenantRegistration = async (e: React.FormEvent) => {
// e.preventDefault();
// if (!validateTenantForm()) {
// return;
// }
// setIsLoading(true);
// setFormErrors({});
// try {
// const registrationData = {
// adminUser: {
// address: "Jakarta",
// email: email,
// fullname: `${firstName} ${lastName}`,
// password: tenantPassword,
// phoneNumber: whatsapp,
// // username: `${firstName}-${lastName}`,
// username: tenantUsername,
// },
// client: {
// clientType: "sub_client",
// name: namaTenant,
// parentClientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead",
// },
// };
// const response = await registerTenant(registrationData);
// if (response.error) {
// MySwal.fire({
// title: "Registration Failed",
// text: response.message || "An error occurred during registration",
// icon: "error",
// confirmButtonText: "OK",
// });
// } else {
// MySwal.fire({
// title: "Registration Successful",
// text: "Your tenant account has been created successfully!",
// icon: "success",
// confirmButtonText: "OK",
// }).then(() => {
// router.push("/auth");
// });
// }
// } catch (error) {
// console.error("Registration error:", error);
// MySwal.fire({
// title: "Registration Failed",
// text: "An unexpected error occurred. Please try again.",
// icon: "error",
// confirmButtonText: "OK",
// });
// } finally {
// setIsLoading(false);
// }
// };
// Generate username otomatis dari nama lengkap
React.useEffect(() => {
if (fullname.trim()) {
@ -685,6 +802,9 @@ export default function SignUp() {
formErrors.firstName ? "border-red-500" : ""
}
/>
<p className="text-[10px] text-gray-500 mt-1">
Hanya huruf dan spasi.
</p>
{formErrors.firstName && (
<p className="text-red-500 text-xs mt-1">
{formErrors.firstName}
@ -710,6 +830,32 @@ export default function SignUp() {
</div>
</div>
<div>
<Input
type="text"
required
placeholder="Username"
value={tenantUsername}
onChange={(e) =>
setTenantUsername(e.target.value.toLowerCase())
}
className={
formErrors.tenantUsername ? "border-red-500" : ""
}
/>
<p className="text-xs text-gray-500 mt-1">
Username harus huruf kecil, tanpa spasi. Contoh:{" "}
<b>netidhub-admin</b>
</p>
{formErrors.tenantUsername && (
<p className="text-red-500 text-xs mt-1">
{formErrors.tenantUsername}
</p>
)}
</div>
<div>
<Input
type="email"

View File

@ -8,29 +8,42 @@ import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
import { close } from "@/config/swal";
import {
createUser,
updateUser,
getUserDetail,
import {
createUser,
updateUser,
getUserDetail,
CreateUserRequest,
UpdateUserRequest
UpdateUserRequest,
} from "@/service/management-user/management-user";
import { getInfoProfile } from "@/service/auth";
import { getUserLevels } from "@/service/approval-workflows";
import { Eye, EyeOff } from "lucide-react";
const createUserSchema = z.object({
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
dateOfBirth: z
.string()
.trim()
.min(1, { message: "Date of Birth wajib diisi" }),
email: z.string().email({ message: "Email tidak valid" }),
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
password: z.string().min(6, { message: "Password minimal 6 karakter" }),
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
phoneNumber: z
.string()
.trim()
.min(1, { message: "Phone Number wajib diisi" }),
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
});
@ -38,11 +51,20 @@ const createUserSchema = z.object({
const editUserSchema = z.object({
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
dateOfBirth: z
.string()
.trim()
.min(1, { message: "Date of Birth wajib diisi" }),
email: z.string().email({ message: "Email tidak valid" }),
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
password: z.string().min(6, { message: "Password minimal 6 karakter" }).optional(),
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
password: z
.string()
.min(6, { message: "Password minimal 6 karakter" })
.optional(),
phoneNumber: z
.string()
.trim()
.min(1, { message: "Phone Number wajib diisi" }),
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
});
@ -67,8 +89,11 @@ export default function UserForm({
}: UserFormProps) {
const MySwal = withReactContent(Swal);
const [loadingData, setLoadingData] = useState(false);
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
const [userLevels, setUserLevels] = useState<{ id: number; name: string }[]>(
[],
);
const [currentClientId, setCurrentClientId] = useState<string>("");
const [showPassword, setShowPassword] = useState(false);
const {
control,
@ -76,7 +101,9 @@ export default function UserForm({
setValue,
formState: { errors },
} = useForm<UserSchema>({
resolver: zodResolver(mode === "create" ? createUserSchema : editUserSchema),
resolver: zodResolver(
mode === "create" ? createUserSchema : editUserSchema,
),
defaultValues: {
address: "",
clientId: "",
@ -106,7 +133,8 @@ export default function UserForm({
// Get clientId from current user info
if (!userInfoResponse?.error && userInfoResponse?.data?.data) {
const userInfo = userInfoResponse.data.data;
const clientId = userInfo.instituteId || "78356d32-52fa-4dfc-b836-6cebf4e3eead"; // fallback to default
const clientId =
userInfo.instituteId || "78356d32-52fa-4dfc-b836-6cebf4e3eead"; // fallback to default
setCurrentClientId(clientId);
setValue("clientId", clientId);
} else {
@ -132,7 +160,10 @@ export default function UserForm({
setValue("username", user.username || "");
// Don't set password for edit mode
} else {
console.error("Gagal mengambil detail user:", userResponse?.message || "Unknown error");
console.error(
"Gagal mengambil detail user:",
userResponse?.message || "Unknown error",
);
}
} catch (error) {
console.error("Error loading user detail:", error);
@ -161,7 +192,7 @@ export default function UserForm({
password: data.password || "",
phoneNumber: data.phoneNumber,
userLevelId: data.userLevelId,
userRoleId: 3, // Hardcoded as per requirement
userRoleId: 3,
username: data.username,
};
@ -177,18 +208,25 @@ export default function UserForm({
close();
if (response?.error) {
errorAutoClose(response.message || `Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
errorAutoClose(
response.message ||
`Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`,
);
return;
}
successAutoClose(`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`);
successAutoClose(
`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`,
);
setTimeout(() => {
if (onSuccess) onSuccess();
}, 3000);
} catch (err) {
close();
errorAutoClose(`Terjadi kesalahan saat ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
errorAutoClose(
`Terjadi kesalahan saat ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`,
);
console.error("User operation error:", err);
}
};
@ -261,6 +299,41 @@ export default function UserForm({
{/* Password - Only show for create mode */}
{mode === "create" && (
<div>
<Label>Password</Label>
<div className="relative">
<Controller
control={control}
name="password"
render={({ field }) => (
<Input
{...field}
type={showPassword ? "text" : "password"}
placeholder="Masukkan password"
className="pr-10"
/>
)}
/>
<button
type="button"
onClick={() => setShowPassword((prev) => !prev)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
{errors.password && (
<p className="text-red-500 text-sm mt-1">
{errors.password.message}
</p>
)}
</div>
)}
{/* {mode === "create" && (
<div>
<Label>Password</Label>
<Controller
@ -274,7 +347,7 @@ export default function UserForm({
<p className="text-red-500 text-sm">{errors.password.message}</p>
)}
</div>
)}
)} */}
{/* Phone Number */}
<div>
@ -287,7 +360,9 @@ export default function UserForm({
)}
/>
{errors.phoneNumber && (
<p className="text-red-500 text-sm">{errors.phoneNumber.message}</p>
<p className="text-red-500 text-sm">
{errors.phoneNumber.message}
</p>
)}
</div>
@ -312,17 +387,17 @@ export default function UserForm({
<Controller
control={control}
name="dateOfBirth"
render={({ field }) => (
<Input {...field} type="date" />
)}
render={({ field }) => <Input {...field} type="date" />}
/>
{errors.dateOfBirth && (
<p className="text-red-500 text-sm">{errors.dateOfBirth.message}</p>
<p className="text-red-500 text-sm">
{errors.dateOfBirth.message}
</p>
)}
</div>
{/* Client ID */}
<div>
{/* <div>
<Label>Client ID</Label>
<Controller
control={control}
@ -342,7 +417,7 @@ export default function UserForm({
<p className="text-xs text-gray-500 mt-1">
Client ID diambil dari profil pengguna yang sedang login
</p>
</div>
</div> */}
{/* User Level */}
<div>
@ -351,7 +426,10 @@ export default function UserForm({
control={control}
name="userLevelId"
render={({ field }) => (
<Select onValueChange={(value) => field.onChange(Number(value))} value={field.value?.toString() || ""}>
<Select
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString() || ""}
>
<SelectTrigger>
<SelectValue placeholder="Pilih user level" />
</SelectTrigger>
@ -372,16 +450,16 @@ export default function UserForm({
)}
/>
{errors.userLevelId && (
<p className="text-red-500 text-sm">{errors.userLevelId.message}</p>
<p className="text-red-500 text-sm">
{errors.userLevelId.message}
</p>
)}
</div>
</div>
{/* Action Buttons */}
<div className="mt-6 flex justify-end gap-3">
<Button type="submit">
{mode === "edit" ? "Update" : "Create"}
</Button>
<Button type="submit">{mode === "edit" ? "Update" : "Create"}</Button>
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
Cancel
</Button>

View File

@ -193,7 +193,7 @@ const UserInternalTable = () => {
};
return (
<>
<div className="bg-white dark:bg-slate-200">
<div className="flex justify-between py-3">
<Input
type="text"
@ -396,7 +396,7 @@ const UserInternalTable = () => {
totalData={totalData}
totalPage={totalPage}
/>
</>
</div>
);
};

View File

@ -49,28 +49,59 @@ export async function getUserLevelMenuActionAccesses(params?: {
limit?: number;
}) {
const queryParams = new URLSearchParams();
if (params?.userLevelId) queryParams.append("user_level_id", params.userLevelId.toString());
if (params?.userLevelId)
queryParams.append("user_level_id", params.userLevelId.toString());
if (params?.menuId) queryParams.append("menu_id", params.menuId.toString());
if (params?.actionCode) queryParams.append("action_code", params.actionCode);
if (params?.canAccess !== undefined) queryParams.append("can_access", params.canAccess.toString());
if (params?.canAccess !== undefined)
queryParams.append("can_access", params.canAccess.toString());
if (params?.page) queryParams.append("page", params.page.toString());
if (params?.limit) queryParams.append("limit", params.limit.toString());
const url = `user-level-menu-action-accesses${queryParams.toString() ? `?${queryParams.toString()}` : ""}`;
const url = `user-level-menu-action-accesses${
queryParams.toString() ? `?${queryParams.toString()}` : ""
}`;
return httpGetInterceptor(url);
}
// export async function getUserLevelMenuActionAccesses(params?: {
// userLevelId?: number;
// menuId?: number;
// actionCode?: string;
// canAccess?: boolean;
// page?: number;
// limit?: number;
// }) {
// const queryParams = new URLSearchParams();
// if (params?.userLevelId) queryParams.append("user_level_id", params.userLevelId.toString());
// if (params?.menuId) queryParams.append("menu_id", params.menuId.toString());
// if (params?.actionCode) queryParams.append("action_code", params.actionCode);
// if (params?.canAccess !== undefined) queryParams.append("can_access", params.canAccess.toString());
// if (params?.page) queryParams.append("page", params.page.toString());
// if (params?.limit) queryParams.append("limit", params.limit.toString());
// const url = `user-level-menu-action-accesses${queryParams.toString() ? `?${queryParams.toString()}` : ""}`;
// return httpGetInterceptor(url);
// }
export async function getUserLevelMenuActionAccessById(id: number) {
const url = `user-level-menu-action-accesses/${id}`;
return httpGetInterceptor(url);
}
export async function getUserLevelMenuActionAccessesByUserLevelId(userLevelId: number) {
export async function getUserLevelMenuActionAccessesByUserLevelId(
userLevelId: number,
) {
const url = `user-level-menu-action-accesses/user-level/${userLevelId}`;
return httpGetInterceptor(url);
}
export async function getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(userLevelId: number, menuId: number) {
export async function getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(
userLevelId: number,
menuId: number,
) {
const url = `user-level-menu-action-accesses/user-level/${userLevelId}/menu/${menuId}`;
return httpGetInterceptor(url);
}
@ -80,22 +111,33 @@ export async function getUserLevelMenuActionAccessesByMenuId(menuId: number) {
return httpGetInterceptor(url);
}
export async function checkUserLevelMenuActionAccess(userLevelId: number, menuId: number, actionCode: string) {
export async function checkUserLevelMenuActionAccess(
userLevelId: number,
menuId: number,
actionCode: string,
) {
const url = `user-level-menu-action-accesses/check/${userLevelId}/${menuId}/${actionCode}`;
return httpGetInterceptor(url);
}
export async function createUserLevelMenuActionAccess(data: UserLevelMenuActionAccessCreateRequest) {
export async function createUserLevelMenuActionAccess(
data: UserLevelMenuActionAccessCreateRequest,
) {
const url = "user-level-menu-action-accesses";
return httpPostInterceptor(url, data);
}
export async function createUserLevelMenuActionAccessesBatch(data: UserLevelMenuActionAccessBatchCreateRequest) {
export async function createUserLevelMenuActionAccessesBatch(
data: UserLevelMenuActionAccessBatchCreateRequest,
) {
const url = "user-level-menu-action-accesses/batch";
return httpPostInterceptor(url, data);
}
export async function updateUserLevelMenuActionAccess(id: number, data: UserLevelMenuActionAccessUpdateRequest) {
export async function updateUserLevelMenuActionAccess(
id: number,
data: UserLevelMenuActionAccessUpdateRequest,
) {
const url = `user-level-menu-action-accesses/${id}`;
return httpPutInterceptor(url, data);
}
@ -104,4 +146,3 @@ export async function deleteUserLevelMenuActionAccess(id: number) {
const url = `user-level-menu-action-accesses/${id}`;
return httpDeleteInterceptor(url);
}

5
service/user.ts Normal file
View File

@ -0,0 +1,5 @@
import { httpGetInterceptor } from "./http-config/http-interceptor-service";
export function getUserInfo() {
return httpGetInterceptor("users/info");
}