diff --git a/app/[locale]/(admin)/admin/content/audio/components/columns.tsx b/app/[locale]/(admin)/admin/content/audio/components/columns.tsx index 43ba06f..2455258 100644 --- a/app/[locale]/(admin)/admin/content/audio/components/columns.tsx +++ b/app/[locale]/(admin)/admin/content/audio/components/columns.tsx @@ -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 = { - 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 ( +
+ + Published + +
+ ); + } + + 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 = { + "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 ( - - {(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}{" "} - +
+ + {label} + +
); }, }, + + // { + // accessorKey: "statusName", + // header: "Status", + // cell: ({ row }) => { + // const statusColors: Record = { + // 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 ( + // + // {(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}{" "} + // + // ); + // }, + // }, { 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 = () => { - - - - View - - + + + + + View + + + + {/* @@ -272,22 +338,31 @@ const useTableColumns = () => { Edit */} - {(Number(row.original.uploadedById) === Number(userId) || - isMabesApprover) && ( + + {/* {(Number(row.original.uploadedById) === Number(userId) || + isMabesApprover) && ( */} + + Edit - )} - handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" - > - - Delete - + + + {/* )} */} + + + handleDeleteMedia(row.original.id)} + className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" + > + + Delete + + + {/* {(row.original.uploadedById === userId || isMabesApprover) && ( handleDeleteMedia(row.original.id)} diff --git a/app/[locale]/(admin)/admin/content/audio/page.tsx b/app/[locale]/(admin)/admin/content/audio/page.tsx index aecd847..c7ecfbd 100644 --- a/app/[locale]/(admin)/admin/content/audio/page.tsx +++ b/app/[locale]/(admin)/admin/content/audio/page.tsx @@ -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 (
@@ -21,18 +25,27 @@ const ReactTableAudioPage = () => {
-

Audio Management

-

Manage your submitted audio files and pending approvals

+

+ Audio Management +

+

+ Manage your submitted audio files and pending approvals +

-
- - - -
+ +
+ + + +
+
@@ -46,4 +59,4 @@ const ReactTableAudioPage = () => { ); }; -export default ReactTableAudioPage; \ No newline at end of file +export default ReactTableAudioPage; diff --git a/app/[locale]/(admin)/admin/content/image/components/columns.tsx b/app/[locale]/(admin)/admin/content/image/components/columns.tsx index 4386755..306277b 100644 --- a/app/[locale]/(admin)/admin/content/image/components/columns.tsx +++ b/app/[locale]/(admin)/admin/content/image/components/columns.tsx @@ -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 (
@@ -193,7 +193,7 @@ const useTableColumns = () => {
); } - + let label = statusName || "Menunggu Review"; if (statusId === 2 && !userHasReviewed && !isCreator) { @@ -217,7 +217,7 @@ const useTableColumns = () => { {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 = () => { - - - - View - - + + + + + View + + + {/* {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( */} - - - - Edit - - + + + + + Edit + + + + {/* )} */} - handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none cursor-pointer" - > - - Delete - + + + handleDeleteMedia(row.original.id)} + className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none cursor-pointer" + > + + Delete + + ); diff --git a/app/[locale]/(admin)/admin/content/image/page.tsx b/app/[locale]/(admin)/admin/content/image/page.tsx index 9f6a350..901b6c6 100644 --- a/app/[locale]/(admin)/admin/content/image/page.tsx +++ b/app/[locale]/(admin)/admin/content/image/page.tsx @@ -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 (
@@ -21,24 +25,33 @@ const ReactTableImagePage = () => {
-

Image Management

-

Manage your submitted images and pending approvals

+

+ Image Management +

+

+ Manage your submitted images and pending approvals +

-
- - - - {/* + +
+ + + + {/* */} -
+
+ diff --git a/app/[locale]/(admin)/admin/content/text/components/columns.tsx b/app/[locale]/(admin)/admin/content/text/components/columns.tsx index ab78050..5a17055 100644 --- a/app/[locale]/(admin)/admin/content/text/components/columns.tsx +++ b/app/[locale]/(admin)/admin/content/text/components/columns.tsx @@ -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 = { - 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 ( +
+ + Published + +
+ ); + } + + 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 = { + "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 ( - - {(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}{" "} - +
+ + {label} + +
); }, }, @@ -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 = () => { - - - - View - - - {(Number(row.original.uploadedById) === Number(userId) || - isMabesApprover) && ( - + + + + View + + + + + {/* {(Number(row.original.uploadedById) === Number(userId) || + isMabesApprover) && ( */} + + + + Edit - )} - handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" - > - - Delete - + + + {/* )} */} + + handleDeleteMedia(row.original.id)} + className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" + > + + Delete + + ); diff --git a/app/[locale]/(admin)/admin/content/text/page.tsx b/app/[locale]/(admin)/admin/content/text/page.tsx index da50a1f..ee951a9 100644 --- a/app/[locale]/(admin)/admin/content/text/page.tsx +++ b/app/[locale]/(admin)/admin/content/text/page.tsx @@ -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 (
{/* */} @@ -21,18 +25,28 @@ const ReactTableDocumentPage = () => {
-

Document Management

-

Manage your submitted documents and pending approvals

+

+ Document Management +

+

+ Manage your submitted documents and pending approvals +

-
- - - -
+ + {" "} +
+ + + +
+
diff --git a/app/[locale]/(admin)/admin/content/video/components/columns.tsx b/app/[locale]/(admin)/admin/content/video/components/columns.tsx index 2ac4292..daaa041 100644 --- a/app/[locale]/(admin)/admin/content/video/components/columns.tsx +++ b/app/[locale]/(admin)/admin/content/video/components/columns.tsx @@ -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 ( +
+ + Published + +
+ ); + } - 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 = { "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 ( - - {label} - +
+ + {label} + +
); }, }, @@ -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 = () => { - - - - View - - + + + + + View + + + + {/* @@ -322,20 +340,27 @@ const useTableColumns = () => { */} {/* {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( */} - - - - Edit - - + + + + + + Edit + + + {/* )} */} - handleDeleteMedia(row.original.id)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" - > - - Delete - + + + handleDeleteMedia(row.original.id)} + className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" + > + + Delete + + + {/* {(row.original.uploadedById === userId || isMabesApprover) && ( handleDeleteMedia(row.original.id)} diff --git a/app/[locale]/(admin)/admin/content/video/components/table-video.tsx b/app/[locale]/(admin)/admin/content/video/components/table-video.tsx index d75f01b..726a36f 100644 --- a/app/[locale]/(admin)/admin/content/video/components/table-video.tsx +++ b/app/[locale]/(admin)/admin/content/video/components/table-video.tsx @@ -72,18 +72,14 @@ const TableVideo = () => { const [columnVisibility, setColumnVisibility] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({}); - const [showData, setShowData] = React.useState("50"); + const [showData, setShowData] = React.useState("10"); const [pagination, setPagination] = React.useState({ 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(""); - const userId = getCookiesDecrypt("uie"); - const userLevelId = getCookiesDecrypt("ulie"); - const [categories, setCategories] = React.useState([]); const [selectedCategories, setSelectedCategories] = React.useState( [] @@ -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({ diff --git a/app/[locale]/(admin)/admin/content/video/page.tsx b/app/[locale]/(admin)/admin/content/video/page.tsx index 5b9ec30..ce98eb3 100644 --- a/app/[locale]/(admin)/admin/content/video/page.tsx +++ b/app/[locale]/(admin)/admin/content/video/page.tsx @@ -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 (
@@ -17,22 +21,32 @@ const ReactTableAudioVisualPage = () => {
-
+
-

Audio-Visual Management

-

Manage your submitted audio-visual files and pending approvals

+

+ Audio-Visual Management +

+

+ Manage your submitted audio-visual files and pending + approvals +

-
- - - -
+ +
+ + + +
+
@@ -46,4 +60,4 @@ const ReactTableAudioVisualPage = () => { ); }; -export default ReactTableAudioVisualPage; \ No newline at end of file +export default ReactTableAudioVisualPage; diff --git a/app/[locale]/(admin)/admin/management-user/page.tsx b/app/[locale]/(admin)/admin/management-user/page.tsx index 2573e52..0e90c8e 100644 --- a/app/[locale]/(admin)/admin/management-user/page.tsx +++ b/app/[locale]/(admin)/admin/management-user/page.tsx @@ -32,7 +32,7 @@ export default function ManagementUser() { return (
-
+

Data User diff --git a/app/[locale]/(admin)/layout.tsx b/app/[locale]/(admin)/layout.tsx index a12ddab..41094c2 100644 --- a/app/[locale]/(admin)/layout.tsx +++ b/app/[locale]/(admin)/layout.tsx @@ -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 ( - - - - - - {children} - - - + {/* 🔐 Permission hanya untuk admin */} + + + + + + + {children} + + + + ); }; 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 ( +// +// +// +// +// +// +// {children} +// +// +// +// +// ); +// }; + +// export default layout; diff --git a/components/access-guard.tsx b/components/access-guard.tsx new file mode 100644 index 0000000..e9c1d91 --- /dev/null +++ b/components/access-guard.tsx @@ -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}; +} diff --git a/components/context/permission-context.tsx b/components/context/permission-context.tsx new file mode 100644 index 0000000..3950b8e --- /dev/null +++ b/components/context/permission-context.tsx @@ -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; +type ActionAccessMap = Record; + +interface PermissionContextType { + canModule: (moduleCode: string) => boolean; + canAction: (actionCode: string) => boolean; + can: (moduleCode: string, actionCode: string) => boolean; + loading: boolean; +} + +const PermissionContext = createContext(null); + +export const PermissionProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [moduleAccess, setModuleAccess] = useState({}); + const [actionCodes, setActionCodes] = useState([]); + 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 = {}; + 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 ( + + {children} + + ); +}; + +export const usePermission = () => { + const ctx = useContext(PermissionContext); + if (!ctx) { + throw new Error("usePermission must be used inside PermissionProvider"); + } + return ctx; +}; diff --git a/components/form/UserLevelsForm.tsx b/components/form/UserLevelsForm.tsx index 31d8c52..d08d46b 100644 --- a/components/form/UserLevelsForm.tsx +++ b/components/form/UserLevelsForm.tsx @@ -3,8 +3,22 @@ import React, { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { ChevronDownIcon, ChevronUpIcon, SaveIcon, EyeIcon, RotateCcwIcon, UsersIcon, HierarchyIcon, PlusIcon, TrashIcon } from "@/components/icons"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { + ChevronDownIcon, + ChevronUpIcon, + SaveIcon, + EyeIcon, + RotateCcwIcon, + UsersIcon, + HierarchyIcon, + PlusIcon, + TrashIcon, +} from "@/components/icons"; import { FormField } from "./common/FormField"; import { DynamicArray } from "./common/DynamicArray"; import { @@ -16,10 +30,7 @@ import { getUserLevels, getProvinces, } from "@/service/approval-workflows"; -import { - MasterMenu, - getMasterMenus, -} from "@/service/menu-modules"; +import { MasterMenu, getMasterMenus } from "@/service/menu-modules"; import { getUserLevelMenuAccessesByUserLevelId, createUserLevelMenuAccessesBatch, @@ -32,10 +43,7 @@ import { deleteUserLevelMenuActionAccess, UserLevelMenuActionAccess, } from "@/service/user-level-menu-action-accesses"; -import { - getMenuActionsByMenuId, - MenuAction, -} from "@/service/menu-actions"; +import { getMenuActionsByMenuId, MenuAction } from "@/service/menu-actions"; import Swal from "sweetalert2"; interface UserLevelsFormProps { @@ -65,19 +73,29 @@ export const UserLevelsForm: React.FC = ({ isActive: true, }); - const [bulkFormData, setBulkFormData] = useState([]); + const [bulkFormData, setBulkFormData] = useState( + [], + ); const [userLevels, setUserLevels] = useState([]); const [provinces, setProvinces] = useState([]); const [menus, setMenus] = useState([]); const [selectedMenuIds, setSelectedMenuIds] = useState([]); - const [userLevelMenuAccesses, setUserLevelMenuAccesses] = useState([]); - const [menuActionsMap, setMenuActionsMap] = useState>({}); - const [selectedActionAccesses, setSelectedActionAccesses] = useState>({}); + const [userLevelMenuAccesses, setUserLevelMenuAccesses] = useState< + UserLevelMenuAccess[] + >([]); + const [menuActionsMap, setMenuActionsMap] = useState< + Record + >({}); + const [selectedActionAccesses, setSelectedActionAccesses] = useState< + Record + >({}); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [expandedHierarchy, setExpandedHierarchy] = useState(false); const [isLoadingData, setIsLoadingData] = useState(true); - const [activeTab, setActiveTab] = useState(mode === "single" ? "basic" : "bulk"); + const [activeTab, setActiveTab] = useState( + mode === "single" ? "basic" : "bulk", + ); useEffect(() => { if (initialData) { @@ -94,18 +112,23 @@ export const UserLevelsForm: React.FC = ({ getMasterMenus({ limit: 100 }), ]); - if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []); + if (!userLevelsRes?.error) + setUserLevels(userLevelsRes?.data?.data || []); if (!provincesRes?.error) setProvinces(provincesRes?.data?.data || []); if (!menusRes?.error) { const menusData = (menusRes?.data?.data || []).map((menu: any) => ({ ...menu, moduleId: menu.module_id || menu.moduleId, - parentMenuId: menu.parent_menu_id !== undefined ? menu.parent_menu_id : menu.parentMenuId, + parentMenuId: + menu.parent_menu_id !== undefined + ? menu.parent_menu_id + : menu.parentMenuId, statusId: menu.status_id || menu.statusId, - isActive: menu.is_active !== undefined ? menu.is_active : menu.isActive, + isActive: + menu.is_active !== undefined ? menu.is_active : menu.isActive, })); setMenus(menusData); - + // Load actions for each menu const actionsMap: Record = {}; for (const menu of menusData) { @@ -115,7 +138,10 @@ export const UserLevelsForm: React.FC = ({ actionsMap[menu.id] = actionsRes?.data?.data || []; } } catch (error) { - console.error(`Error loading actions for menu ${menu.id}:`, error); + console.error( + `Error loading actions for menu ${menu.id}:`, + error, + ); } } setMenuActionsMap(actionsMap); @@ -136,17 +162,28 @@ export const UserLevelsForm: React.FC = ({ const userLevelId = (initialData as any).id; try { // Load menu accesses - const menuRes = await getUserLevelMenuAccessesByUserLevelId(userLevelId); + const menuRes = + await getUserLevelMenuAccessesByUserLevelId(userLevelId); if (!menuRes?.error) { const menuAccesses = menuRes?.data?.data || []; setUserLevelMenuAccesses(menuAccesses); - setSelectedMenuIds(menuAccesses.filter((a: UserLevelMenuAccess) => a.canAccess).map((a: UserLevelMenuAccess) => a.menuId)); - + setSelectedMenuIds( + menuAccesses + .filter((a: UserLevelMenuAccess) => a.canAccess) + .map((a: UserLevelMenuAccess) => a.menuId), + ); + // Load action accesses for each menu const actionAccesses: Record = {}; - for (const menuAccess of menuAccesses.filter((a: UserLevelMenuAccess) => a.canAccess)) { + for (const menuAccess of menuAccesses.filter( + (a: UserLevelMenuAccess) => a.canAccess, + )) { try { - const actionRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(userLevelId, menuAccess.menuId); + const actionRes = + await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId( + userLevelId, + menuAccess.menuId, + ); if (!actionRes?.error) { const actions = actionRes?.data?.data || []; actionAccesses[menuAccess.menuId] = actions @@ -154,7 +191,10 @@ export const UserLevelsForm: React.FC = ({ .map((a: UserLevelMenuActionAccess) => a.actionCode); } } catch (error) { - console.error(`Error loading action accesses for menu ${menuAccess.menuId}:`, error); + console.error( + `Error loading action accesses for menu ${menuAccess.menuId}:`, + error, + ); } } setSelectedActionAccesses(actionAccesses); @@ -168,7 +208,9 @@ export const UserLevelsForm: React.FC = ({ loadAccesses(); }, [initialData]); - const validateForm = (data: UserLevelsCreateRequest): Record => { + const validateForm = ( + data: UserLevelsCreateRequest, + ): Record => { const newErrors: Record = {}; if (!data.name.trim()) { @@ -202,7 +244,7 @@ export const UserLevelsForm: React.FC = ({ bulkFormData.forEach((data, index) => { const itemErrors = validateForm(data); - Object.keys(itemErrors).forEach(key => { + Object.keys(itemErrors).forEach((key) => { newErrors[`${index}.${key}`] = itemErrors[key]; }); if (Object.keys(itemErrors).length > 0) { @@ -214,14 +256,21 @@ export const UserLevelsForm: React.FC = ({ return isValid; }; - const handleFieldChange = (field: keyof UserLevelsCreateRequest, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); + const handleFieldChange = ( + field: keyof UserLevelsCreateRequest, + value: any, + ) => { + setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { - setErrors(prev => ({ ...prev, [field]: "" })); + setErrors((prev) => ({ ...prev, [field]: "" })); } }; - const handleBulkFieldChange = (index: number, field: keyof UserLevelsCreateRequest, value: any) => { + const handleBulkFieldChange = ( + index: number, + field: keyof UserLevelsCreateRequest, + value: any, + ) => { setBulkFormData((prev: UserLevelsCreateRequest[]) => { const newData = [...prev]; newData[index] = { ...newData[index], [field]: value }; @@ -247,10 +296,15 @@ export const UserLevelsForm: React.FC = ({ isApprovalActive: true, isActive: true, }; - setBulkFormData(prev => [...prev, newItem]); + setBulkFormData((prev) => [...prev, newItem]); }; - const renderBulkItemForm = (item: UserLevelsCreateRequest, index: number, onUpdate: (item: UserLevelsCreateRequest) => void, onDelete: () => void) => { + const renderBulkItemForm = ( + item: UserLevelsCreateRequest, + index: number, + onUpdate: (item: UserLevelsCreateRequest) => void, + onDelete: () => void, + ) => { return (

@@ -271,7 +325,9 @@ export const UserLevelsForm: React.FC = ({ type="text" placeholder="e.g., SENIOR_EDITOR, MANAGER, PUBLISHER" value={item.aliasName} - onChange={(value) => onUpdate({ ...item, aliasName: value.toUpperCase() })} + onChange={(value) => + onUpdate({ ...item, aliasName: value.toUpperCase() }) + } error={errors[`${index}.aliasName`]} required helpText="Short identifier for system use" @@ -283,7 +339,9 @@ export const UserLevelsForm: React.FC = ({ type="number" placeholder="e.g., 1, 2, 3" value={item.levelNumber} - onChange={(value) => onUpdate({ ...item, levelNumber: value ? Number(value) : 1 })} + onChange={(value) => + onUpdate({ ...item, levelNumber: value ? Number(value) : 1 }) + } error={errors[`${index}.levelNumber`]} required min={1} @@ -296,17 +354,32 @@ export const UserLevelsForm: React.FC = ({ label="Parent Level" name={`parentLevelId-${index}`} type="select" - placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"} + placeholder={ + userLevels.length > 0 + ? "Select parent level" + : "No parent levels available" + } value={item.parentLevelId} - onChange={(value) => onUpdate({ ...item, parentLevelId: value !== undefined ? Number(value) : undefined })} + onChange={(value) => + onUpdate({ + ...item, + parentLevelId: value !== undefined ? Number(value) : undefined, + }) + } options={[ { value: 0, label: "No Parent (Root Level)" }, - ...(userLevels.length > 0 ? userLevels.map(level => ({ - value: level.id, - label: `${level.name} (Level ${level.levelNumber})`, - })) : []) + ...(userLevels.length > 0 + ? userLevels.map((level) => ({ + value: level.id, + label: `${level.name} (Level ${level.levelNumber})`, + })) + : []), ]} - helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"} + helpText={ + userLevels.length === 0 + ? "No parent levels found. This will be a root level." + : "Select parent level for hierarchy" + } disabled={userLevels.length === 0} /> @@ -314,14 +387,31 @@ export const UserLevelsForm: React.FC = ({ label="Province" name={`provinceId-${index}`} type="select" - placeholder={provinces.length > 0 ? "Select province" : "No provinces available"} + placeholder={ + provinces.length > 0 + ? "Select province" + : "No provinces available" + } value={item.provinceId} - onChange={(value) => onUpdate({ ...item, provinceId: value ? Number(value) : undefined })} - options={provinces.length > 0 ? provinces.map(province => ({ - value: province.id, - label: province.prov_name, - })) : []} - helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"} + onChange={(value) => + onUpdate({ + ...item, + provinceId: value ? Number(value) : undefined, + }) + } + options={ + provinces.length > 0 + ? provinces.map((province) => ({ + value: province.id, + label: province.prov_name, + })) + : [] + } + helpText={ + provinces.length === 0 + ? "No provinces found. Please ensure provinces are available in the system." + : "Geographic scope for this level" + } disabled={provinces.length === 0} />
@@ -361,314 +451,478 @@ export const UserLevelsForm: React.FC = ({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - - // Determine current mode based on active tab - const currentMode = activeTab === "bulk" ? "bulk" : "single"; - - if (currentMode === "single") { - const validationErrors = validateForm(formData); - console.log("Form mode: ", currentMode); - console.log("Error single ", validationErrors); + if (isSubmitting) return; // 🔒 HARD LOCK + + setIsSubmitting(true); + + try { + const isBulkMode = activeTab === "bulk"; + const isEditing = Boolean((initialData as any)?.id); + const userLevelId = (initialData as any)?.id; + + /* =============================== + * BULK MODE + * =============================== */ + if (isBulkMode) { + if (!validateBulkForm()) { + Swal.fire({ + title: "Validation Error", + text: "Please fix the errors before submitting", + icon: "error", + }); + return; + } + + let successCount = 0; + let failedCount = 0; + + for (const item of bulkFormData) { + try { + const res = await createUserLevel(item); + if (res?.error) { + failedCount++; + } else { + successCount++; + } + } catch { + failedCount++; + } + } + + Swal.fire({ + title: failedCount === 0 ? "Success" : "Partial Success", + text: + failedCount === 0 + ? `${successCount} user levels created successfully` + : `${successCount} success, ${failedCount} failed`, + icon: failedCount === 0 ? "success" : "warning", + }).then(() => { + window.location.reload(); + }); + + return; // ⛔ STOP HERE (PENTING) + } + + /* =============================== + * SINGLE MODE + * =============================== */ + + const validationErrors = validateForm(formData); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); Swal.fire({ title: "Validation Error", text: "Please fix the errors before submitting", icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } }); return; } - } else { - if (!validateBulkForm()) { + + let response; + if (isEditing) { + response = await updateUserLevel(userLevelId, formData); + } else { + response = await createUserLevel(formData); + } + + if (response?.error) { Swal.fire({ - title: "Validation Error", - text: "Please fix the errors before submitting", + title: "Error", + text: response?.message || "Failed to save user level", icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } }); return; } - } - setIsSubmitting(true); - - try { - if (onSave) { - if (currentMode === "single") { - // Check if editing or creating - const isEditing = !!(initialData as any)?.id; - const userLevelId = (initialData as any)?.id; - - // Save user level first - let userLevelResponse; - if (isEditing) { - userLevelResponse = await updateUserLevel(userLevelId, formData); - } else { - userLevelResponse = await createUserLevel(formData); - } - - if (userLevelResponse?.error) { - Swal.fire({ - title: "Error", - text: userLevelResponse?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, - icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); - setIsSubmitting(false); - return; - } + const createdUserLevelId = response?.data?.data?.id || userLevelId; - // Get the user level ID - const createdUserLevelId = userLevelResponse?.data?.data?.id || userLevelId; - - // Save menu accesses - if (createdUserLevelId && selectedMenuIds.length > 0) { - // Delete existing menu accesses if editing - if ((initialData as any)?.id) { - for (const access of userLevelMenuAccesses) { - await deleteUserLevelMenuAccess(access.id); - } - } + /* =============================== + * MENU ACCESS + * =============================== */ - // Create new menu accesses in batch - const menuAccessResponse = await createUserLevelMenuAccessesBatch({ - userLevelId: createdUserLevelId, - menuIds: selectedMenuIds, - }); - - if (menuAccessResponse?.error) { - console.error("Error saving menu accesses:", menuAccessResponse?.message); - } else { - // Save action accesses for each menu - for (const menuId of selectedMenuIds) { - const actionCodes = selectedActionAccesses[menuId] || []; - - // Delete existing action accesses for this menu if editing - if ((initialData as any)?.id) { - try { - const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); - if (!existingActionsRes?.error) { - const existingActions = existingActionsRes?.data?.data || []; - for (const action of existingActions) { - await deleteUserLevelMenuActionAccess(action.id); - } - } - } catch (error) { - console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); - } - } - - // Create new action accesses in batch - if (actionCodes.length > 0) { - const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ - userLevelId: createdUserLevelId, - menuId: menuId, - actionCodes: actionCodes, - }); - - if (actionAccessResponse?.error) { - console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); - } - } - } - } - } - - onSave(formData); - } else { - // For bulk mode, save each item individually - let hasErrors = false; - let successCount = 0; - - for (const item of bulkFormData) { - const response = await createUserLevel(item); - - if (response?.error) { - hasErrors = true; - Swal.fire({ - title: "Error", - text: `Failed to create user level "${item.name}": ${response?.message || "Unknown error"}`, - icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); - } else { - successCount++; - Swal.fire({ - title: "Success", - text: `User level "${item.name}" created successfully`, - icon: "success", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); - } - } - - // Refresh page if at least one item was created successfully - if (successCount > 0) { - setTimeout(() => { - window.location.reload(); - }, 1000); // Small delay to let user see the success message + if (createdUserLevelId) { + if (isEditing) { + for (const access of userLevelMenuAccesses) { + await deleteUserLevelMenuAccess(access.id); } } - } else { - if (currentMode === "single") { - // Check if editing or creating - const isEditing = !!(initialData as any)?.id; - const userLevelId = (initialData as any)?.id; - - let response; + + if (selectedMenuIds.length > 0) { + await createUserLevelMenuAccessesBatch({ + userLevelId: createdUserLevelId, + menuIds: selectedMenuIds, + }); + + for (const menuId of selectedMenuIds) { + const actionCodes = selectedActionAccesses[menuId] || []; + if (isEditing) { - response = await updateUserLevel(userLevelId, formData); - } else { - response = await createUserLevel(formData); - } - console.log(`${isEditing ? 'Update' : 'Create'} Response: `, response); - - if (response?.error) { - Swal.fire({ - title: "Error", - text: response?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, - icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); - } else { - // Get the user level ID - const createdUserLevelId = response?.data?.data?.id || userLevelId; - - // Save menu accesses - if (createdUserLevelId && selectedMenuIds.length > 0) { - // Delete existing menu accesses if editing - if ((initialData as any)?.id) { - for (const access of userLevelMenuAccesses) { - await deleteUserLevelMenuAccess(access.id); - } - } + const existing = + await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId( + createdUserLevelId, + menuId, + ); - // Create new menu accesses in batch - const menuAccessResponse = await createUserLevelMenuAccessesBatch({ + for (const a of existing?.data?.data || []) { + await deleteUserLevelMenuActionAccess(a.id); + } + } + + if (actionCodes.length > 0) { + await createUserLevelMenuActionAccessesBatch({ userLevelId: createdUserLevelId, - menuIds: selectedMenuIds, + menuId, + actionCodes, }); - - if (menuAccessResponse?.error) { - console.error("Error saving menu accesses:", menuAccessResponse?.message); - } else { - // Save action accesses for each menu - for (const menuId of selectedMenuIds) { - const actionCodes = selectedActionAccesses[menuId] || []; - - // Delete existing action accesses for this menu if editing - if ((initialData as any)?.id) { - try { - const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); - if (!existingActionsRes?.error) { - const existingActions = existingActionsRes?.data?.data || []; - for (const action of existingActions) { - await deleteUserLevelMenuActionAccess(action.id); - } - } - } catch (error) { - console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); - } - } - - // Create new action accesses in batch - if (actionCodes.length > 0) { - const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ - userLevelId: createdUserLevelId, - menuId: menuId, - actionCodes: actionCodes, - }); - - if (actionAccessResponse?.error) { - console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); - } - } - } - } } - - Swal.fire({ - title: "Success", - text: isEditing ? "User level updated successfully" : "User level created successfully", - icon: "success", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }).then(() => { - // Refresh page after successful save - window.location.reload(); - }); - } - } else { - // Bulk creation - const promises = bulkFormData.map(item => createUserLevel(item)); - const responses = await Promise.all(promises); - - console.log("Create Responses: ", responses); - - const failedCount = responses.filter((r: any) => r.error).length; - const successCount = responses.length - failedCount; - - if (failedCount === 0) { - Swal.fire({ - title: "Success", - text: `All ${successCount} user levels created successfully`, - icon: "success", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); - } else { - Swal.fire({ - title: "Partial Success", - text: `${successCount} user levels created successfully, ${failedCount} failed`, - icon: "warning", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } - }); } } } - } catch (error) { - console.error("Error submitting form:", error); + + // Swal.fire({ + // title: "Success", + // text: isEditing + // ? "User level updated successfully" + // : "User level created successfully", + // icon: "success", + // }).then(() => { + // window.location.reload(); + // }); + Swal.fire({ + icon: "success", + title: "Sukses", + text: isEditing + ? "User level updated successfully" + : "User level created successfully", + timer: 1500, + showConfirmButton: false, + allowOutsideClick: false, + }).then(() => { + window.location.reload(); + }); + } catch (err) { + console.error(err); Swal.fire({ title: "Error", - text: "An unexpected error occurred", + text: "Unexpected error occurred", icon: "error", - confirmButtonText: "OK", - customClass: { - popup: 'swal-z-index-9999' - } }); } finally { setIsSubmitting(false); } }; + // const handleSubmit = async (e: React.FormEvent) => { + // e.preventDefault(); + + // // Determine current mode based on active tab + // const currentMode = activeTab === "bulk" ? "bulk" : "single"; + + // if (currentMode === "single") { + // const validationErrors = validateForm(formData); + // console.log("Form mode: ", currentMode); + // console.log("Error single ", validationErrors); + + // if (Object.keys(validationErrors).length > 0) { + // setErrors(validationErrors); + // Swal.fire({ + // title: "Validation Error", + // text: "Please fix the errors before submitting", + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // return; + // } + // } else { + // if (!validateBulkForm()) { + // Swal.fire({ + // title: "Validation Error", + // text: "Please fix the errors before submitting", + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // return; + // } + // } + + // setIsSubmitting(true); + + // try { + // if (onSave) { + // if (currentMode === "single") { + // // Check if editing or creating + // const isEditing = !!(initialData as any)?.id; + // const userLevelId = (initialData as any)?.id; + + // // Save user level first + // let userLevelResponse; + // if (isEditing) { + // userLevelResponse = await updateUserLevel(userLevelId, formData); + // } else { + // userLevelResponse = await createUserLevel(formData); + // } + + // if (userLevelResponse?.error) { + // Swal.fire({ + // title: "Error", + // text: userLevelResponse?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // setIsSubmitting(false); + // return; + // } + + // // Get the user level ID + // const createdUserLevelId = userLevelResponse?.data?.data?.id || userLevelId; + + // // Save menu accesses + // if (createdUserLevelId && selectedMenuIds.length > 0) { + // // Delete existing menu accesses if editing + // if ((initialData as any)?.id) { + // for (const access of userLevelMenuAccesses) { + // await deleteUserLevelMenuAccess(access.id); + // } + // } + + // // Create new menu accesses in batch + // const menuAccessResponse = await createUserLevelMenuAccessesBatch({ + // userLevelId: createdUserLevelId, + // menuIds: selectedMenuIds, + // }); + + // if (menuAccessResponse?.error) { + // console.error("Error saving menu accesses:", menuAccessResponse?.message); + // } else { + // // Save action accesses for each menu + // for (const menuId of selectedMenuIds) { + // const actionCodes = selectedActionAccesses[menuId] || []; + + // // Delete existing action accesses for this menu if editing + // if ((initialData as any)?.id) { + // try { + // const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); + // if (!existingActionsRes?.error) { + // const existingActions = existingActionsRes?.data?.data || []; + // for (const action of existingActions) { + // await deleteUserLevelMenuActionAccess(action.id); + // } + // } + // } catch (error) { + // console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); + // } + // } + + // // Create new action accesses in batch + // if (actionCodes.length > 0) { + // const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ + // userLevelId: createdUserLevelId, + // menuId: menuId, + // actionCodes: actionCodes, + // }); + + // if (actionAccessResponse?.error) { + // console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); + // } + // } + // } + // } + // } + + // onSave(formData); + // } else { + // // For bulk mode, save each item individually + // let hasErrors = false; + // let successCount = 0; + + // for (const item of bulkFormData) { + // const response = await createUserLevel(item); + + // if (response?.error) { + // hasErrors = true; + // Swal.fire({ + // title: "Error", + // text: `Failed to create user level "${item.name}": ${response?.message || "Unknown error"}`, + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } else { + // successCount++; + // Swal.fire({ + // title: "Success", + // text: `User level "${item.name}" created successfully`, + // icon: "success", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } + // } + + // // Refresh page if at least one item was created successfully + // if (successCount > 0) { + // setTimeout(() => { + // window.location.reload(); + // }, 1000); // Small delay to let user see the success message + // } + // } + // } else { + // if (currentMode === "single") { + // // Check if editing or creating + // const isEditing = !!(initialData as any)?.id; + // const userLevelId = (initialData as any)?.id; + + // let response; + // if (isEditing) { + // response = await updateUserLevel(userLevelId, formData); + // } else { + // response = await createUserLevel(formData); + // } + // console.log(`${isEditing ? 'Update' : 'Create'} Response: `, response); + + // if (response?.error) { + // Swal.fire({ + // title: "Error", + // text: response?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } else { + // // Get the user level ID + // const createdUserLevelId = response?.data?.data?.id || userLevelId; + + // // Save menu accesses + // if (createdUserLevelId && selectedMenuIds.length > 0) { + // // Delete existing menu accesses if editing + // if ((initialData as any)?.id) { + // for (const access of userLevelMenuAccesses) { + // await deleteUserLevelMenuAccess(access.id); + // } + // } + + // // Create new menu accesses in batch + // const menuAccessResponse = await createUserLevelMenuAccessesBatch({ + // userLevelId: createdUserLevelId, + // menuIds: selectedMenuIds, + // }); + + // if (menuAccessResponse?.error) { + // console.error("Error saving menu accesses:", menuAccessResponse?.message); + // } else { + // // Save action accesses for each menu + // for (const menuId of selectedMenuIds) { + // const actionCodes = selectedActionAccesses[menuId] || []; + + // // Delete existing action accesses for this menu if editing + // if ((initialData as any)?.id) { + // try { + // const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); + // if (!existingActionsRes?.error) { + // const existingActions = existingActionsRes?.data?.data || []; + // for (const action of existingActions) { + // await deleteUserLevelMenuActionAccess(action.id); + // } + // } + // } catch (error) { + // console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); + // } + // } + + // // Create new action accesses in batch + // if (actionCodes.length > 0) { + // const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ + // userLevelId: createdUserLevelId, + // menuId: menuId, + // actionCodes: actionCodes, + // }); + + // if (actionAccessResponse?.error) { + // console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); + // } + // } + // } + // } + // } + + // Swal.fire({ + // title: "Success", + // text: isEditing ? "User level updated successfully" : "User level created successfully", + // icon: "success", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }).then(() => { + // // Refresh page after successful save + // window.location.reload(); + // }); + // } + // } else { + // // Bulk creation + // const promises = bulkFormData.map(item => createUserLevel(item)); + // const responses = await Promise.all(promises); + + // console.log("Create Responses: ", responses); + + // const failedCount = responses.filter((r: any) => r.error).length; + // const successCount = responses.length - failedCount; + + // if (failedCount === 0) { + // Swal.fire({ + // title: "Success", + // text: `All ${successCount} user levels created successfully`, + // icon: "success", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } else { + // Swal.fire({ + // title: "Partial Success", + // text: `${successCount} user levels created successfully, ${failedCount} failed`, + // icon: "warning", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } + // } + // } + // } catch (error) { + // console.error("Error submitting form:", error); + // Swal.fire({ + // title: "Error", + // text: "An unexpected error occurred", + // icon: "error", + // confirmButtonText: "OK", + // customClass: { + // popup: 'swal-z-index-9999' + // } + // }); + // } finally { + // setIsSubmitting(false); + // } + // }; + const handleReset = () => { Swal.fire({ title: "Reset Form", @@ -678,8 +932,8 @@ export const UserLevelsForm: React.FC = ({ confirmButtonText: "Yes, reset", cancelButtonText: "Cancel", customClass: { - popup: 'swal-z-index-9999' - } + popup: "swal-z-index-9999", + }, }).then((result) => { if (result.isConfirmed) { if (mode === "single") { @@ -704,22 +958,27 @@ export const UserLevelsForm: React.FC = ({ const renderHierarchyTree = () => { const buildTree = (levels: UserLevel[], parentId?: number): UserLevel[] => { return levels - .filter(level => level.parentLevelId === parentId) - .map(level => ({ + .filter((level) => level.parentLevelId === parentId) + .map((level) => ({ ...level, - children: buildTree(levels, level.id) + children: buildTree(levels, level.id), })); }; const tree = buildTree(userLevels); - const renderNode = (node: UserLevel & { children?: UserLevel[] }, depth = 0) => ( + const renderNode = ( + node: UserLevel & { children?: UserLevel[] }, + depth = 0, + ) => (
{node.name} (Level {node.levelNumber}) - {node.group && - {node.group}} + {node.group && ( + - {node.group} + )}
{node.children?.map((child: any) => renderNode(child, depth + 1))} @@ -727,9 +986,7 @@ export const UserLevelsForm: React.FC = ({ ); return ( -
- {tree.map(node => renderNode(node))} -
+
{tree.map((node) => renderNode(node))}
); }; @@ -741,13 +998,31 @@ export const UserLevelsForm: React.FC = ({ Loading form data...
)} - - + + - Basic Information - Menu Access - Action Access - Bulk Operations + + Basic Information + + + Menu Access + + + Action Access + + + Bulk Operations + {/* Basic Information Tab */} @@ -775,7 +1050,9 @@ export const UserLevelsForm: React.FC = ({ type="text" placeholder="e.g., SENIOR_EDITOR, MANAGER, PUBLISHER" value={formData.aliasName} - onChange={(value) => handleFieldChange("aliasName", value.toUpperCase())} + onChange={(value) => + handleFieldChange("aliasName", value.toUpperCase()) + } error={errors.aliasName} required helpText="Short identifier for system use" @@ -787,7 +1064,9 @@ export const UserLevelsForm: React.FC = ({ type="number" placeholder="e.g., 1, 2, 3" value={formData.levelNumber} - onChange={(value) => handleFieldChange("levelNumber", value ? Number(value) : 1)} + onChange={(value) => + handleFieldChange("levelNumber", value ? Number(value) : 1) + } error={errors.levelNumber} required min={1} @@ -800,17 +1079,32 @@ export const UserLevelsForm: React.FC = ({ label="Parent Level" name="parentLevelId" type="select" - placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"} + placeholder={ + userLevels.length > 0 + ? "Select parent level" + : "No parent levels available" + } value={formData.parentLevelId} - onChange={(value) => handleFieldChange("parentLevelId", value !== undefined ? Number(value) : undefined)} + onChange={(value) => + handleFieldChange( + "parentLevelId", + value !== undefined ? Number(value) : undefined, + ) + } options={[ { value: 0, label: "No Parent (Root Level)" }, - ...(userLevels.length > 0 ? userLevels.map(level => ({ - value: level.id, - label: `${level.name} (Level ${level.levelNumber})`, - })) : []) + ...(userLevels.length > 0 + ? userLevels.map((level) => ({ + value: level.id, + label: `${level.name} (Level ${level.levelNumber})`, + })) + : []), ]} - helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"} + helpText={ + userLevels.length === 0 + ? "No parent levels found. This will be a root level." + : "Select parent level for hierarchy" + } disabled={userLevels.length === 0} /> @@ -818,14 +1112,31 @@ export const UserLevelsForm: React.FC = ({ label="Province" name="provinceId" type="select" - placeholder={provinces.length > 0 ? "Select province" : "No provinces available"} + placeholder={ + provinces.length > 0 + ? "Select province" + : "No provinces available" + } value={formData.provinceId} - onChange={(value) => handleFieldChange("provinceId", value ? Number(value) : undefined)} - options={provinces.length > 0 ? provinces.map(province => ({ - value: province.id, - label: province.prov_name, - })) : []} - helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"} + onChange={(value) => + handleFieldChange( + "provinceId", + value ? Number(value) : undefined, + ) + } + options={ + provinces.length > 0 + ? provinces.map((province) => ({ + value: province.id, + label: province.prov_name, + })) + : [] + } + helpText={ + provinces.length === 0 + ? "No provinces found. Please ensure provinces are available in the system." + : "Geographic scope for this level" + } disabled={provinces.length === 0} />
@@ -846,7 +1157,9 @@ export const UserLevelsForm: React.FC = ({ name="isApprovalActive" type="checkbox" value={formData.isApprovalActive} - onChange={(value) => handleFieldChange("isApprovalActive", value)} + onChange={(value) => + handleFieldChange("isApprovalActive", value) + } helpText="Users with this level can participate in approval process" /> @@ -902,7 +1215,8 @@ export const UserLevelsForm: React.FC = ({

- Select which menus this user level can access. Users with this level will only see selected menus in the navigation. + Select which menus this user level can access. Users with this + level will only see selected menus in the navigation.

{menus.length > 0 ? (
@@ -919,16 +1233,19 @@ export const UserLevelsForm: React.FC = ({ const newMenuIds = prev.includes(menu.id) ? prev.filter((id) => id !== menu.id) : [...prev, menu.id]; - + // If menu is deselected, remove its action accesses - if (prev.includes(menu.id) && !newMenuIds.includes(menu.id)) { + if ( + prev.includes(menu.id) && + !newMenuIds.includes(menu.id) + ) { setSelectedActionAccesses((prevActions) => { const newActions = { ...prevActions }; delete newActions[menu.id]; return newActions; }); } - + return newMenuIds; }); }} @@ -936,7 +1253,9 @@ export const UserLevelsForm: React.FC = ({ />
{menu.name}
-
{menu.description}
+
+ {menu.description} +
Group: {menu.group} • Module ID: {menu.moduleId}
@@ -961,22 +1280,31 @@ export const UserLevelsForm: React.FC = ({

- Configure which actions users with this level can perform in each menu. First select menus in the "Menu Access" tab. + Configure which actions users with this level can perform in + each menu. First select menus in the "Menu Access" tab.

{selectedMenuIds.length > 0 ? (
{selectedMenuIds.map((menuId) => { const menu = menus.find((m) => m.id === menuId); const actions = menuActionsMap[menuId] || []; - const selectedActions = selectedActionAccesses[menuId] || []; + const selectedActions = + selectedActionAccesses[menuId] || []; if (!menu) return null; return ( - + - {menu.name} -

{menu.description}

+ + {menu.name} + +

+ {menu.description} +

{actions.length > 0 ? ( @@ -988,12 +1316,19 @@ export const UserLevelsForm: React.FC = ({ > { setSelectedActionAccesses((prev) => { const current = prev[menuId] || []; - const newActions = current.includes(action.actionCode) - ? current.filter((code) => code !== action.actionCode) + const newActions = current.includes( + action.actionCode, + ) + ? current.filter( + (code) => + code !== action.actionCode, + ) : [...current, action.actionCode]; return { ...prev, @@ -1004,10 +1339,13 @@ export const UserLevelsForm: React.FC = ({ className="mt-1" />
-
{action.actionName}
+
+ {action.actionName} +
Code: {action.actionCode} - {action.pathUrl && ` • Path: ${action.pathUrl}`} + {action.pathUrl && + ` • Path: ${action.pathUrl}`}
@@ -1026,7 +1364,9 @@ export const UserLevelsForm: React.FC = ({ ) : (

No menus selected

-

Please select menus in the "Menu Access" tab first

+

+ Please select menus in the "Menu Access" tab first +

)}
@@ -1060,12 +1400,18 @@ export const UserLevelsForm: React.FC = ({ {bulkFormData.length === 0 ? (
-

No user levels added yet. Add multiple levels to create them in bulk.

+

+ No user levels added yet. Add multiple levels to create + them in bulk. +

) : (
{bulkFormData.map((item, index) => ( - +
@@ -1076,7 +1422,9 @@ export const UserLevelsForm: React.FC = ({ variant="outline" size="sm" onClick={() => { - const newItems = bulkFormData.filter((_, i) => i !== index); + const newItems = bulkFormData.filter( + (_, i) => i !== index, + ); setBulkFormData(newItems); }} className="text-red-600 hover:text-red-700" @@ -1095,9 +1443,11 @@ export const UserLevelsForm: React.FC = ({ setBulkFormData(newItems); }, () => { - const newItems = bulkFormData.filter((_, i) => i !== index); + const newItems = bulkFormData.filter( + (_, i) => i !== index, + ); setBulkFormData(newItems); - } + }, )} @@ -1135,15 +1485,23 @@ export const UserLevelsForm: React.FC = ({ Cancel )} - - + + {/* + {isSubmitting + ? "Saving..." + : isLoadingData + ? "Loading..." + : `Save ${activeTab === "bulk" ? "User Levels" : "User Level"}`} + */}
diff --git a/components/form/content/audio-visual/video-detail-form.tsx b/components/form/content/audio-visual/video-detail-form.tsx index e868c0c..69865eb 100644 --- a/components/form/content/audio-visual/video-detail-form.tsx +++ b/components/form/content/audio-visual/video-detail-form.tsx @@ -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
Memuat data...
; + 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 (
@@ -325,7 +335,7 @@ export default function FormVideoDetail() {
- + )} - {(Number(detail.needApprovalFromLevel || 0) == + {/* {isPending && + (Number(detail.needApprovalFromLevel || 0) === + Number(userLevelId) || + detail.isPublish === false) && + Number(detail.uploadedById || detail.createdById) !== + Number(userId) ? ( */} + + {isPending && ( + +
+ + + + + +
+
+ )} + + {/* ) : 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() { Reject
- ) : null} + ) : null} */} diff --git a/components/form/content/audio/audio-detail-form.tsx b/components/form/content/audio/audio-detail-form.tsx index 458d442..744a9b6 100644 --- a/components/form/content/audio/audio-detail-form.tsx +++ b/components/form/content/audio/audio-detail-form.tsx @@ -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 ( {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() { setupPlacement(index, "mabes", Boolean(e)) @@ -681,7 +690,7 @@ export default function FormAudioDetail() { setupPlacement(index, "polda", Boolean(e)) @@ -699,13 +708,13 @@ export default function FormAudioDetail() { setupPlacement( index, "international", - Boolean(e) + Boolean(e), ) } /> @@ -821,30 +830,34 @@ export default function FormAudioDetail() { Number(detail?.uploadedById) == Number(userId) ? ( "" ) : ( */} -
- - - -
+ {isPending && ( + +
+ + + +
+
+ )} {/* ) ) : ( "" diff --git a/components/form/content/document/teks-detail-form.tsx b/components/form/content/document/teks-detail-form.tsx index 574dfb2..06f4731 100644 --- a/components/form/content/document/teks-detail-form.tsx +++ b/components/form/content/document/teks-detail-form.tsx @@ -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 ( {detail !== undefined ? ( @@ -528,10 +531,10 @@ export default function FormTeksDetail() {
-
- - -
+
+ + +
{/*
@@ -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() { setupPlacement(index, "mabes", Boolean(e)) @@ -681,7 +684,7 @@ export default function FormTeksDetail() { setupPlacement(index, "polda", Boolean(e)) @@ -699,13 +702,13 @@ export default function FormTeksDetail() { setupPlacement( index, "international", - Boolean(e) + Boolean(e), ) } /> @@ -817,10 +820,17 @@ export default function FormTeksDetail() {
- {/* {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 && ( +
+ +
- {/* ) +
+ )} + {/* ) ) : ( "" )} */} diff --git a/components/form/content/image/image-detail-form.tsx b/components/form/content/image/image-detail-form.tsx index a07473d..5957427 100644 --- a/components/form/content/image/image-detail-form.tsx +++ b/components/form/content/image/image-detail-form.tsx @@ -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; const [selectedFiles, setSelectedFiles] = useState([]); @@ -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 ( {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), ) && ( {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() { setupPlacement( index, "mabes", - Boolean(e) + Boolean(e), ) } /> @@ -953,13 +961,13 @@ export default function FormImageDetail() { setupPlacement( index, "polda", - Boolean(e) + Boolean(e), ) } /> @@ -984,13 +992,13 @@ export default function FormImageDetail() { setupPlacement( index, "international", - Boolean(e) + Boolean(e), ) } /> @@ -1111,15 +1119,22 @@ export default function FormImageDetail() { - {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 && ( +
- ) +
+ )} + {/* ) ) : ( "" - )} + )} */}
) : ( diff --git a/components/form/sign-up.tsx b/components/form/sign-up.tsx index 8ada1c3..219594a 100644 --- a/components/form/sign-up.tsx +++ b/components/form/sign-up.tsx @@ -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" : "" } /> +

+ Hanya huruf dan spasi. +

{formErrors.firstName && (

{formErrors.firstName} @@ -710,6 +830,32 @@ export default function SignUp() {

+
+ + setTenantUsername(e.target.value.toLowerCase()) + } + className={ + formErrors.tenantUsername ? "border-red-500" : "" + } + /> + +

+ Username harus huruf kecil, tanpa spasi. Contoh:{" "} + netidhub-admin +

+ + {formErrors.tenantUsername && ( +

+ {formErrors.tenantUsername} +

+ )} +
+
([]); + const [userLevels, setUserLevels] = useState<{ id: number; name: string }[]>( + [], + ); const [currentClientId, setCurrentClientId] = useState(""); + const [showPassword, setShowPassword] = useState(false); const { control, @@ -76,7 +101,9 @@ export default function UserForm({ setValue, formState: { errors }, } = useForm({ - 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" && ( +
+ + +
+ ( + + )} + /> + + +
+ + {errors.password && ( +

+ {errors.password.message} +

+ )} +
+ )} + + {/* {mode === "create" && (
{errors.password.message}

)}
- )} + )} */} {/* Phone Number */}
@@ -287,7 +360,9 @@ export default function UserForm({ )} /> {errors.phoneNumber && ( -

{errors.phoneNumber.message}

+

+ {errors.phoneNumber.message} +

)}
@@ -312,17 +387,17 @@ export default function UserForm({ ( - - )} + render={({ field }) => } /> {errors.dateOfBirth && ( -

{errors.dateOfBirth.message}

+

+ {errors.dateOfBirth.message} +

)}
{/* Client ID */} -
+ {/*
Client ID diambil dari profil pengguna yang sedang login

-
+
*/} {/* User Level */}
@@ -351,7 +426,10 @@ export default function UserForm({ control={control} name="userLevelId" render={({ field }) => ( - field.onChange(Number(value))} + value={field.value?.toString() || ""} + > @@ -372,16 +450,16 @@ export default function UserForm({ )} /> {errors.userLevelId && ( -

{errors.userLevelId.message}

+

+ {errors.userLevelId.message} +

)}
{/* Action Buttons */}
- + diff --git a/components/table/management-user/management-user-internal-table.tsx b/components/table/management-user/management-user-internal-table.tsx index 527e5a7..6fefbf5 100644 --- a/components/table/management-user/management-user-internal-table.tsx +++ b/components/table/management-user/management-user-internal-table.tsx @@ -193,7 +193,7 @@ const UserInternalTable = () => { }; return ( - <> +
{ totalData={totalData} totalPage={totalPage} /> - +
); }; diff --git a/service/user-level-menu-action-accesses.ts b/service/user-level-menu-action-accesses.ts index 022af0a..ed4efac 100644 --- a/service/user-level-menu-action-accesses.ts +++ b/service/user-level-menu-action-accesses.ts @@ -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); } - diff --git a/service/user.ts b/service/user.ts new file mode 100644 index 0000000..f82bdeb --- /dev/null +++ b/service/user.ts @@ -0,0 +1,5 @@ +import { httpGetInterceptor } from "./http-config/http-interceptor-service"; + +export function getUserInfo() { + return httpGetInterceptor("users/info"); +}