fix: fixing bugs in userlevels
This commit is contained in:
parent
b6bde7722e
commit
fc1d4a6b9a
|
|
@ -17,6 +17,7 @@ import { deleteArticle, deleteMedia } from "@/service/content/content";
|
||||||
import { error } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -137,57 +138,119 @@ const useTableColumns = () => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "statusName",
|
accessorKey: "statusName",
|
||||||
header: "Status",
|
header: "Status",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const statusColors: Record<string, string> = {
|
const {
|
||||||
diterima: "bg-green-100 text-green-600",
|
statusId,
|
||||||
"menunggu review": "bg-orange-100 text-orange-600",
|
statusName,
|
||||||
|
isPublish,
|
||||||
|
reviewedAtLevel = "",
|
||||||
|
creatorGroupLevelId,
|
||||||
|
needApprovalFromLevel,
|
||||||
|
} = row.original;
|
||||||
|
|
||||||
|
const userLevelId = Number(getCookiesDecrypt("ulie"));
|
||||||
|
|
||||||
|
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
||||||
|
const isCreator = Number(creatorGroupLevelId) === userLevelId;
|
||||||
|
|
||||||
|
if (isPublish) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
|
<Badge className="flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 bg-green-100 text-green-700 text-center whitespace-nowrap">
|
||||||
|
Published
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let label = statusName || "Menunggu Review";
|
||||||
|
|
||||||
|
if (statusId === 2 && !userHasReviewed && !isCreator) {
|
||||||
|
label = "Menunggu Review";
|
||||||
|
} else if (statusId === 1 && needApprovalFromLevel === userLevelId) {
|
||||||
|
label = "Menunggu Review";
|
||||||
|
} else if (statusId === 2) {
|
||||||
|
label = "Diterima";
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors: Record<string, string> = {
|
||||||
|
"Menunggu Review": "bg-orange-100 text-orange-600",
|
||||||
|
Diterima: "bg-blue-100 text-blue-600",
|
||||||
|
Published: "bg-green-100 text-green-700",
|
||||||
|
Unknown: "bg-gray-100 text-gray-600",
|
||||||
|
default: "bg-gray-100 text-gray-600",
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = [
|
|
||||||
"bg-orange-100 text-orange-600",
|
|
||||||
"bg-orange-100 text-orange-600",
|
|
||||||
"bg-green-100 text-green-600",
|
|
||||||
"bg-blue-100 text-blue-600",
|
|
||||||
"bg-red-200 text-red-600",
|
|
||||||
];
|
|
||||||
|
|
||||||
const status =
|
|
||||||
Number(row.original?.statusId) == 2 &&
|
|
||||||
row.original?.reviewedAtLevel !== null &&
|
|
||||||
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
|
|
||||||
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
|
|
||||||
? "1"
|
|
||||||
: row.original?.statusId;
|
|
||||||
const statusStyles =
|
|
||||||
colors[Number(status)] || "bg-red-200 text-red-600";
|
|
||||||
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
className={cn(
|
<Badge
|
||||||
"rounded-full px-5 w-full whitespace-nowrap",
|
className={cn(
|
||||||
statusStyles
|
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
|
||||||
)}
|
colors[label] || colors.default,
|
||||||
>
|
)}
|
||||||
{(Number(row.original?.statusId) == 2 &&
|
>
|
||||||
!row.original?.reviewedAtLevel !== null &&
|
{label}
|
||||||
!row.original?.reviewedAtLevel?.includes(
|
</Badge>
|
||||||
`:${Number(userLevelId)}:`
|
</div>
|
||||||
) &&
|
|
||||||
Number(row.original?.creatorGroupLevelId) !=
|
|
||||||
Number(userLevelId)) ||
|
|
||||||
(Number(row.original?.statusId) == 1 &&
|
|
||||||
Number(row.original?.needApprovalFromLevel) ==
|
|
||||||
Number(userLevelId))
|
|
||||||
? "Menunggu Review"
|
|
||||||
: row.original?.statusName}{" "}
|
|
||||||
</Badge>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// accessorKey: "statusName",
|
||||||
|
// header: "Status",
|
||||||
|
// cell: ({ row }) => {
|
||||||
|
// const statusColors: Record<string, string> = {
|
||||||
|
// diterima: "bg-green-100 text-green-600",
|
||||||
|
// "menunggu review": "bg-orange-100 text-orange-600",
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const colors = [
|
||||||
|
// "bg-orange-100 text-orange-600",
|
||||||
|
// "bg-orange-100 text-orange-600",
|
||||||
|
// "bg-green-100 text-green-600",
|
||||||
|
// "bg-blue-100 text-blue-600",
|
||||||
|
// "bg-red-200 text-red-600",
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// const status =
|
||||||
|
// Number(row.original?.statusId) == 2 &&
|
||||||
|
// row.original?.reviewedAtLevel !== null &&
|
||||||
|
// !row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
|
||||||
|
// Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
|
||||||
|
// ? "1"
|
||||||
|
// : row.original?.statusId;
|
||||||
|
// const statusStyles =
|
||||||
|
// colors[Number(status)] || "bg-red-200 text-red-600";
|
||||||
|
// // const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Badge
|
||||||
|
// className={cn(
|
||||||
|
// "rounded-full px-5 w-full whitespace-nowrap",
|
||||||
|
// statusStyles
|
||||||
|
// )}
|
||||||
|
// >
|
||||||
|
// {(Number(row.original?.statusId) == 2 &&
|
||||||
|
// !row.original?.reviewedAtLevel !== null &&
|
||||||
|
// !row.original?.reviewedAtLevel?.includes(
|
||||||
|
// `:${Number(userLevelId)}:`
|
||||||
|
// ) &&
|
||||||
|
// Number(row.original?.creatorGroupLevelId) !=
|
||||||
|
// Number(userLevelId)) ||
|
||||||
|
// (Number(row.original?.statusId) == 1 &&
|
||||||
|
// Number(row.original?.needApprovalFromLevel) ==
|
||||||
|
// Number(userLevelId))
|
||||||
|
// ? "Menunggu Review"
|
||||||
|
// : row.original?.statusName}{" "}
|
||||||
|
// </Badge>
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
|
|
@ -241,7 +304,7 @@ const useTableColumns = () => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (userLevelId !== undefined && roleId !== undefined) {
|
if (userLevelId !== undefined && roleId !== undefined) {
|
||||||
setIsMabesApprover(
|
setIsMabesApprover(
|
||||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
Number(userLevelId) == 216 && Number(roleId) == 3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [userLevelId, roleId]);
|
}, [userLevelId, roleId]);
|
||||||
|
|
@ -258,12 +321,15 @@ const useTableColumns = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<Link href={`/admin/content/audio/detail/${row.original.id}`}>
|
<AccessGuard action="view">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<Link href={`/admin/content/audio/detail/${row.original.id}`}>
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
View
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
</DropdownMenuItem>
|
View
|
||||||
</Link>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
{/* <Link
|
{/* <Link
|
||||||
href={`/admin/content/audio/update/${row.original.id}`}
|
href={`/admin/content/audio/update/${row.original.id}`}
|
||||||
>
|
>
|
||||||
|
|
@ -272,22 +338,31 @@ const useTableColumns = () => {
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link> */}
|
</Link> */}
|
||||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
|
||||||
isMabesApprover) && (
|
{/* {(Number(row.original.uploadedById) === Number(userId) ||
|
||||||
|
isMabesApprover) && ( */}
|
||||||
|
|
||||||
|
<AccessGuard action="edit">
|
||||||
<Link href={`/admin/content/audio/update/${row.original.id}`}>
|
<Link href={`/admin/content/audio/update/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
</AccessGuard>
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
{/* )} */}
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
|
||||||
>
|
<AccessGuard action="delete">
|
||||||
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
<DropdownMenuItem
|
||||||
Delete
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
</DropdownMenuItem>
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import AudioTabs from "./components/audio-tabs";
|
||||||
import { UploadIcon } from "lucide-react";
|
import { UploadIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePermission } from "@/components/context/permission-context";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const ReactTableAudioPage = () => {
|
const ReactTableAudioPage = () => {
|
||||||
|
const { can } = usePermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
|
|
@ -21,18 +25,27 @@ const ReactTableAudioPage = () => {
|
||||||
<UploadIcon className="w-4 h-4 text-blue-600" />
|
<UploadIcon className="w-4 h-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">Audio Management</h1>
|
<h1 className="text-xl font-semibold text-gray-900">
|
||||||
<p className="text-sm text-gray-500">Manage your submitted audio files and pending approvals</p>
|
Audio Management
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Manage your submitted audio files and pending approvals
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<AccessGuard action="create">
|
||||||
<Link href={"/admin/content/audio/create"}>
|
<div className="flex-none">
|
||||||
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
|
<Link href={"/admin/content/audio/create"}>
|
||||||
<UploadIcon size={18} className="mr-2" />
|
<Button
|
||||||
Create Audio
|
color="primary"
|
||||||
</Button>
|
className="text-white shadow-sm hover:shadow-md transition-shadow"
|
||||||
</Link>
|
>
|
||||||
</div>
|
<UploadIcon size={18} className="mr-2" />
|
||||||
|
Create Audio
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
@ -46,4 +59,4 @@ const ReactTableAudioPage = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReactTableAudioPage;
|
export default ReactTableAudioPage;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { deleteArticle } from "@/service/content/content";
|
import { deleteArticle } from "@/service/content/content";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -183,7 +184,6 @@ const useTableColumns = () => {
|
||||||
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
||||||
const isCreator = Number(creatorGroupLevelId) === userLevelId;
|
const isCreator = Number(creatorGroupLevelId) === userLevelId;
|
||||||
|
|
||||||
|
|
||||||
if (isPublish) {
|
if (isPublish) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center w-full h-full">
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
|
|
@ -193,7 +193,7 @@ const useTableColumns = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = statusName || "Menunggu Review";
|
let label = statusName || "Menunggu Review";
|
||||||
|
|
||||||
if (statusId === 2 && !userHasReviewed && !isCreator) {
|
if (statusId === 2 && !userHasReviewed && !isCreator) {
|
||||||
|
|
@ -217,7 +217,7 @@ const useTableColumns = () => {
|
||||||
<Badge
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
|
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
|
||||||
colors[label] || colors.default
|
colors[label] || colors.default,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
|
@ -281,7 +281,7 @@ const useTableColumns = () => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (userLevelId !== undefined && roleId !== undefined) {
|
if (userLevelId !== undefined && roleId !== undefined) {
|
||||||
setIsMabesApprover(
|
setIsMabesApprover(
|
||||||
Number(userLevelId) === 216 && Number(roleId) === 3
|
Number(userLevelId) === 216 && Number(roleId) === 3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [userLevelId, roleId]);
|
}, [userLevelId, roleId]);
|
||||||
|
|
@ -298,30 +298,38 @@ const useTableColumns = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0 hover:text-black" align="end">
|
<DropdownMenuContent className="p-0 hover:text-black" align="end">
|
||||||
<Link
|
<AccessGuard action="view">
|
||||||
href={`/admin/content/image/detail/${row.original.id}`}
|
<Link
|
||||||
className="hover:text-black"
|
href={`/admin/content/image/detail/${row.original.id}`}
|
||||||
>
|
className="hover:text-black"
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
|
>
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
|
||||||
View
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
</DropdownMenuItem>
|
View
|
||||||
</Link>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
{/* {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( */}
|
{/* {(Number(row.original.uploadedById) === Number(userId) || isMabesApprover) && ( */}
|
||||||
<Link href={`/admin/content/image/update/${row.original.id}`}>
|
<AccessGuard action="edit">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
|
<Link href={`/admin/content/image/update/${row.original.id}`}>
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
<DropdownMenuItem className="p-2 border-b text-default-700 rounded-none cursor-pointer hover:bg-slate-200">
|
||||||
Edit
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
</DropdownMenuItem>
|
Edit
|
||||||
</Link>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
<AccessGuard action="delete">
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none cursor-pointer"
|
<DropdownMenuItem
|
||||||
>
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none cursor-pointer"
|
||||||
Delete
|
>
|
||||||
</DropdownMenuItem>
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AccessGuard>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import ImageTabs from "./components/image-tabs";
|
||||||
import { UploadIcon } from "lucide-react";
|
import { UploadIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePermission } from "@/components/context/permission-context";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const ReactTableImagePage = () => {
|
const ReactTableImagePage = () => {
|
||||||
|
const { can } = usePermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
|
|
@ -21,24 +25,33 @@ const ReactTableImagePage = () => {
|
||||||
<UploadIcon className="w-4 h-4 text-blue-600" />
|
<UploadIcon className="w-4 h-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">Image Management</h1>
|
<h1 className="text-xl font-semibold text-gray-900">
|
||||||
<p className="text-sm text-gray-500">Manage your submitted images and pending approvals</p>
|
Image Management
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Manage your submitted images and pending approvals
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<AccessGuard action="create">
|
||||||
<Link href={"/admin/content/image/create"}>
|
<div className="flex-none">
|
||||||
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow cursor-pointer">
|
<Link href={"/admin/content/image/create"}>
|
||||||
<UploadIcon size={18} className="mr-2" />
|
<Button
|
||||||
Create Image
|
color="primary"
|
||||||
</Button>
|
className="text-white shadow-sm hover:shadow-md transition-shadow cursor-pointer"
|
||||||
</Link>
|
>
|
||||||
{/* <Link href={"/contributor/content/image/createAi"}>
|
<UploadIcon size={18} className="mr-2" />
|
||||||
|
Create Image
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
{/* <Link href={"/contributor/content/image/createAi"}>
|
||||||
<Button color="primary" className="text-white ml-3">
|
<Button color="primary" className="text-white ml-3">
|
||||||
<UploadIcon />
|
<UploadIcon />
|
||||||
Unggah Foto Dengan AI
|
Unggah Foto Dengan AI
|
||||||
</Button>
|
</Button>
|
||||||
</Link> */}
|
</Link> */}
|
||||||
</div>
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { deleteArticle, deleteMedia } from "@/service/content/content";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -142,50 +143,59 @@ const useTableColumns = () => {
|
||||||
accessorKey: "statusName",
|
accessorKey: "statusName",
|
||||||
header: "Status",
|
header: "Status",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const statusColors: Record<string, string> = {
|
const {
|
||||||
diterima: "bg-green-100 text-green-600",
|
statusId,
|
||||||
"menunggu review": "bg-orange-100 text-orange-600",
|
statusName,
|
||||||
|
isPublish,
|
||||||
|
reviewedAtLevel = "",
|
||||||
|
creatorGroupLevelId,
|
||||||
|
needApprovalFromLevel,
|
||||||
|
} = row.original;
|
||||||
|
|
||||||
|
const userLevelId = Number(getCookiesDecrypt("ulie"));
|
||||||
|
|
||||||
|
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
||||||
|
const isCreator = Number(creatorGroupLevelId) === userLevelId;
|
||||||
|
|
||||||
|
if (isPublish) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
|
<Badge className="flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 bg-green-100 text-green-700 text-center whitespace-nowrap">
|
||||||
|
Published
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let label = statusName || "Menunggu Review";
|
||||||
|
|
||||||
|
if (statusId === 2 && !userHasReviewed && !isCreator) {
|
||||||
|
label = "Menunggu Review";
|
||||||
|
} else if (statusId === 1 && needApprovalFromLevel === userLevelId) {
|
||||||
|
label = "Menunggu Review";
|
||||||
|
} else if (statusId === 2) {
|
||||||
|
label = "Diterima";
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors: Record<string, string> = {
|
||||||
|
"Menunggu Review": "bg-orange-100 text-orange-600",
|
||||||
|
Diterima: "bg-blue-100 text-blue-600",
|
||||||
|
Published: "bg-green-100 text-green-700",
|
||||||
|
Unknown: "bg-gray-100 text-gray-600",
|
||||||
|
default: "bg-gray-100 text-gray-600",
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = [
|
|
||||||
"bg-orange-100 text-orange-600",
|
|
||||||
"bg-orange-100 text-orange-600",
|
|
||||||
"bg-green-100 text-green-600",
|
|
||||||
"bg-blue-100 text-blue-600",
|
|
||||||
"bg-red-200 text-red-600",
|
|
||||||
];
|
|
||||||
|
|
||||||
const status =
|
|
||||||
Number(row.original?.statusId) == 2 &&
|
|
||||||
row.original?.reviewedAtLevel !== null &&
|
|
||||||
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
|
|
||||||
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
|
|
||||||
? "1"
|
|
||||||
: row.original?.statusId;
|
|
||||||
const statusStyles =
|
|
||||||
colors[Number(status)] || "bg-red-200 text-red-600";
|
|
||||||
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
className={cn(
|
<Badge
|
||||||
"rounded-full px-5 w-full whitespace-nowrap",
|
className={cn(
|
||||||
statusStyles
|
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
|
||||||
)}
|
colors[label] || colors.default,
|
||||||
>
|
)}
|
||||||
{(Number(row.original?.statusId) == 2 &&
|
>
|
||||||
!row.original?.reviewedAtLevel !== null &&
|
{label}
|
||||||
!row.original?.reviewedAtLevel?.includes(
|
</Badge>
|
||||||
`:${Number(userLevelId)}:`
|
</div>
|
||||||
) &&
|
|
||||||
Number(row.original?.creatorGroupLevelId) !=
|
|
||||||
Number(userLevelId)) ||
|
|
||||||
(Number(row.original?.statusId) == 1 &&
|
|
||||||
Number(row.original?.needApprovalFromLevel) ==
|
|
||||||
Number(userLevelId))
|
|
||||||
? "Menunggu Review"
|
|
||||||
: row.original?.statusName}{" "}
|
|
||||||
</Badge>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -242,7 +252,7 @@ const useTableColumns = () => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (userLevelId !== undefined && roleId !== undefined) {
|
if (userLevelId !== undefined && roleId !== undefined) {
|
||||||
setIsMabesApprover(
|
setIsMabesApprover(
|
||||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
Number(userLevelId) == 216 && Number(roleId) == 3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [userLevelId, roleId]);
|
}, [userLevelId, roleId]);
|
||||||
|
|
@ -259,28 +269,37 @@ const useTableColumns = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<Link href={`/admin/content/text/detail/${row.original.id}`}>
|
<AccessGuard action="view">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<Link href={`/admin/content/text/detail/${row.original.id}`}>
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
|
||||||
isMabesApprover) && (
|
|
||||||
<Link href={`/admin/content/text/update/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
|
View
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
|
{/* {(Number(row.original.uploadedById) === Number(userId) ||
|
||||||
|
isMabesApprover) && ( */}
|
||||||
|
|
||||||
|
<AccessGuard action="edit">
|
||||||
|
<Link href={`/admin/content/text/update/${row.original.id}`}>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
</AccessGuard>
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
{/* )} */}
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
<AccessGuard action="delete">
|
||||||
>
|
<DropdownMenuItem
|
||||||
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
Delete
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
||||||
</DropdownMenuItem>
|
>
|
||||||
|
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AccessGuard>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import DocumentTabs from "./components/document-tabs";
|
||||||
import { UploadIcon } from "lucide-react";
|
import { UploadIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePermission } from "@/components/context/permission-context";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const ReactTableDocumentPage = () => {
|
const ReactTableDocumentPage = () => {
|
||||||
|
const { can } = usePermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* <SiteBreadcrumb /> */}
|
{/* <SiteBreadcrumb /> */}
|
||||||
|
|
@ -21,18 +25,28 @@ const ReactTableDocumentPage = () => {
|
||||||
<UploadIcon className="w-4 h-4 text-blue-600" />
|
<UploadIcon className="w-4 h-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">Document Management</h1>
|
<h1 className="text-xl font-semibold text-gray-900">
|
||||||
<p className="text-sm text-gray-500">Manage your submitted documents and pending approvals</p>
|
Document Management
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Manage your submitted documents and pending approvals
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<AccessGuard action="create">
|
||||||
<Link href={"/admin/content/text/create"}>
|
{" "}
|
||||||
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
|
<div className="flex-none">
|
||||||
<UploadIcon size={18} className="mr-2" />
|
<Link href={"/admin/content/text/create"}>
|
||||||
Create Document
|
<Button
|
||||||
</Button>
|
color="primary"
|
||||||
</Link>
|
className="text-white shadow-sm hover:shadow-md transition-shadow"
|
||||||
</div>
|
>
|
||||||
|
<UploadIcon size={18} className="mr-2" />
|
||||||
|
Create Document
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { error } from "@/lib/swal";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { deleteMedia } from "@/service/content";
|
import { deleteMedia } from "@/service/content";
|
||||||
import { deleteArticle } from "@/service/content/content";
|
import { deleteArticle } from "@/service/content/content";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -194,45 +195,59 @@ const useTableColumns = () => {
|
||||||
accessorKey: "statusName",
|
accessorKey: "statusName",
|
||||||
header: "Status",
|
header: "Status",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const statusId = Number(row.original?.statusId);
|
const {
|
||||||
const reviewedAtLevel = row.original?.reviewedAtLevel || "";
|
statusId,
|
||||||
const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId);
|
statusName,
|
||||||
const needApprovalFromLevel = Number(
|
isPublish,
|
||||||
row.original?.needApprovalFromLevel
|
reviewedAtLevel = "",
|
||||||
);
|
creatorGroupLevelId,
|
||||||
|
needApprovalFromLevel,
|
||||||
|
} = row.original;
|
||||||
|
|
||||||
|
const userLevelId = Number(getCookiesDecrypt("ulie"));
|
||||||
|
|
||||||
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`);
|
||||||
const isCreator = creatorGroupLevelId === Number(userLevelId);
|
const isCreator = Number(creatorGroupLevelId) === userLevelId;
|
||||||
|
|
||||||
const isWaitingForReview =
|
if (isPublish) {
|
||||||
statusId === 2 && !userHasReviewed && !isCreator;
|
return (
|
||||||
const isApprovalNeeded =
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
statusId === 1 && needApprovalFromLevel === Number(userLevelId);
|
<Badge className="flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 bg-green-100 text-green-700 text-center whitespace-nowrap">
|
||||||
|
Published
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const label =
|
let label = statusName || "Menunggu Review";
|
||||||
isWaitingForReview || isApprovalNeeded
|
|
||||||
? "Menunggu Review"
|
if (statusId === 2 && !userHasReviewed && !isCreator) {
|
||||||
: statusId === 2
|
label = "Menunggu Review";
|
||||||
? "Diterima"
|
} else if (statusId === 1 && needApprovalFromLevel === userLevelId) {
|
||||||
: row.original?.statusName;
|
label = "Menunggu Review";
|
||||||
|
} else if (statusId === 2) {
|
||||||
|
label = "Diterima";
|
||||||
|
}
|
||||||
|
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
"Menunggu Review": "bg-orange-100 text-orange-600",
|
"Menunggu Review": "bg-orange-100 text-orange-600",
|
||||||
Diterima: "bg-green-100 text-green-600",
|
Diterima: "bg-blue-100 text-blue-600",
|
||||||
default: "bg-red-200 text-red-600",
|
Published: "bg-green-100 text-green-700",
|
||||||
|
Unknown: "bg-gray-100 text-gray-600",
|
||||||
|
default: "bg-gray-100 text-gray-600",
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusStyles = colors[label] || colors.default;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
className={cn(
|
<Badge
|
||||||
"rounded-full px-5 w-full whitespace-nowrap",
|
className={cn(
|
||||||
statusStyles
|
"flex items-center justify-center min-w-[120px] rounded-full px-5 py-1 text-center whitespace-nowrap",
|
||||||
)}
|
colors[label] || colors.default,
|
||||||
>
|
)}
|
||||||
{label}
|
>
|
||||||
</Badge>
|
{label}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -289,7 +304,7 @@ const useTableColumns = () => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (userLevelId !== undefined && roleId !== undefined) {
|
if (userLevelId !== undefined && roleId !== undefined) {
|
||||||
setIsMabesApprover(
|
setIsMabesApprover(
|
||||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
Number(userLevelId) == 216 && Number(roleId) == 3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [userLevelId, roleId]);
|
}, [userLevelId, roleId]);
|
||||||
|
|
@ -306,12 +321,15 @@ const useTableColumns = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<Link href={`/admin/content/video/detail/${row.original.id}`}>
|
<AccessGuard action="view">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<Link href={`/admin/content/video/detail/${row.original.id}`}>
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
View
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
</DropdownMenuItem>
|
View
|
||||||
</Link>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
{/* <Link
|
{/* <Link
|
||||||
href={`/contributor/content/video/update/${row.original.id}`}
|
href={`/contributor/content/video/update/${row.original.id}`}
|
||||||
>
|
>
|
||||||
|
|
@ -322,20 +340,27 @@ const useTableColumns = () => {
|
||||||
</Link> */}
|
</Link> */}
|
||||||
{/* {(Number(row.original.uploadedById) === Number(userId) ||
|
{/* {(Number(row.original.uploadedById) === Number(userId) ||
|
||||||
isMabesApprover) && ( */}
|
isMabesApprover) && ( */}
|
||||||
<Link href={`/admin/content/video/update/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
<AccessGuard action="edit">
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
<Link href={`/admin/content/video/update/${row.original.id}`}>
|
||||||
Edit
|
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
|
||||||
</DropdownMenuItem>
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
</Link>
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</AccessGuard>
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
<AccessGuard action="delete">
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
<DropdownMenuItem
|
||||||
>
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
|
||||||
Delete
|
>
|
||||||
</DropdownMenuItem>
|
<Trash2 className="w-4 h-4 me-1.5 focus:text-white" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AccessGuard>
|
||||||
|
|
||||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,14 @@ const TableVideo = () => {
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
const [showData, setShowData] = React.useState("50");
|
const [showData, setShowData] = React.useState("10");
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: Number(showData),
|
pageSize: Number(showData),
|
||||||
});
|
});
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [limit, setLimit] = React.useState(10);
|
|
||||||
const [search, setSearch] = React.useState<string>("");
|
const [search, setSearch] = React.useState<string>("");
|
||||||
const userId = getCookiesDecrypt("uie");
|
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
|
||||||
|
|
||||||
const [categories, setCategories] = React.useState<any[]>([]);
|
const [categories, setCategories] = React.useState<any[]>([]);
|
||||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||||
[]
|
[]
|
||||||
|
|
@ -95,7 +91,6 @@ const TableVideo = () => {
|
||||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||||
const [filterBySource, setFilterBySource] = React.useState("");
|
const [filterBySource, setFilterBySource] = React.useState("");
|
||||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||||
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
const columns = useTableColumns();
|
const columns = useTableColumns();
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import AudioVisualTabs from "./components/audio-visual-tabs";
|
||||||
import { UploadIcon } from "lucide-react";
|
import { UploadIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePermission } from "@/components/context/permission-context";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const ReactTableAudioVisualPage = () => {
|
const ReactTableAudioVisualPage = () => {
|
||||||
|
const { can } = usePermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
|
|
@ -17,22 +21,32 @@ const ReactTableAudioVisualPage = () => {
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-black rounded-lg flex items-center justify-center">
|
||||||
<UploadIcon className="w-4 h-4 text-blue-600" />
|
<UploadIcon className="w-4 h-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">Audio-Visual Management</h1>
|
<h1 className="text-xl font-semibold text-gray-900">
|
||||||
<p className="text-sm text-gray-500">Manage your submitted audio-visual files and pending approvals</p>
|
Audio-Visual Management
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Manage your submitted audio-visual files and pending
|
||||||
|
approvals
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<AccessGuard action="create">
|
||||||
<Link href={"/admin/content/video/create"}>
|
<div className="flex-none">
|
||||||
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
|
<Link href={"/admin/content/video/create"}>
|
||||||
<UploadIcon size={18} className="mr-2" />
|
<Button
|
||||||
Create Audio-Visual
|
color="primary"
|
||||||
</Button>
|
className="text-white shadow-sm hover:shadow-md transition-shadow"
|
||||||
</Link>
|
>
|
||||||
</div>
|
<UploadIcon size={18} className="mr-2" />
|
||||||
|
Create Audio-Visual
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
@ -46,4 +60,4 @@ const ReactTableAudioVisualPage = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReactTableAudioVisualPage;
|
export default ReactTableAudioVisualPage;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default function ManagementUser() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<section className="flex flex-col gap-2 bg-slate-50 dark:bg-black rounded-lg p-3 mt-5 border">
|
<section className="flex flex-col gap-2 bg-white dark:bg-slate-200 rounded-lg p-3 mt-5 border">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
Data User
|
Data User
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,52 @@ import ThemeCustomize from "@/components/partials/customizer";
|
||||||
import DashCodeHeader from "@/components/partials/header";
|
import DashCodeHeader from "@/components/partials/header";
|
||||||
import MountedProvider from "@/providers/mounted.provider";
|
import MountedProvider from "@/providers/mounted.provider";
|
||||||
import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
|
import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
|
||||||
|
import { PermissionProvider } from "@/components/context/permission-context";
|
||||||
|
|
||||||
const layout = async ({ children }: { children: React.ReactNode }) => {
|
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<MountedProvider isProtected={true}>
|
<MountedProvider isProtected={true}>
|
||||||
<LayoutProvider>
|
{/* 🔐 Permission hanya untuk admin */}
|
||||||
<WorkflowModalProvider>
|
<PermissionProvider>
|
||||||
<ThemeCustomize />
|
<LayoutProvider>
|
||||||
<DashCodeHeader />
|
<WorkflowModalProvider>
|
||||||
<DashCodeSidebar />
|
<ThemeCustomize />
|
||||||
<LayoutContentProvider>{children}</LayoutContentProvider>
|
<DashCodeHeader />
|
||||||
<DashCodeFooter />
|
<DashCodeSidebar />
|
||||||
</WorkflowModalProvider>
|
<LayoutContentProvider>{children}</LayoutContentProvider>
|
||||||
</LayoutProvider>
|
<DashCodeFooter />
|
||||||
|
</WorkflowModalProvider>
|
||||||
|
</LayoutProvider>
|
||||||
|
</PermissionProvider>
|
||||||
</MountedProvider>
|
</MountedProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default layout;
|
export default layout;
|
||||||
|
|
||||||
|
// import LayoutProvider from "@/providers/layout.provider";
|
||||||
|
// import LayoutContentProvider from "@/providers/content.provider";
|
||||||
|
// import DashCodeSidebar from "@/components/partials/sidebar";
|
||||||
|
// import DashCodeFooter from "@/components/partials/footer";
|
||||||
|
// import ThemeCustomize from "@/components/partials/customizer";
|
||||||
|
// import DashCodeHeader from "@/components/partials/header";
|
||||||
|
// import MountedProvider from "@/providers/mounted.provider";
|
||||||
|
// import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
|
||||||
|
|
||||||
|
// const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
|
// return (
|
||||||
|
// <MountedProvider isProtected={true}>
|
||||||
|
// <LayoutProvider>
|
||||||
|
// <WorkflowModalProvider>
|
||||||
|
// <ThemeCustomize />
|
||||||
|
// <DashCodeHeader />
|
||||||
|
// <DashCodeSidebar />
|
||||||
|
// <LayoutContentProvider>{children}</LayoutContentProvider>
|
||||||
|
// <DashCodeFooter />
|
||||||
|
// </WorkflowModalProvider>
|
||||||
|
// </LayoutProvider>
|
||||||
|
// </MountedProvider>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default layout;
|
||||||
|
|
|
||||||
|
|
@ -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}</>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
import { getUserLevelModuleAccessesByUserLevelId } from "@/service/user-level-module-accesses";
|
||||||
|
import { getUserLevelMenuActionAccesses } from "@/service/user-level-menu-action-accesses";
|
||||||
|
import { getUserInfo } from "@/service/user";
|
||||||
|
|
||||||
|
type ModuleAccessMap = Record<string, boolean>;
|
||||||
|
type ActionAccessMap = Record<string, string[]>;
|
||||||
|
|
||||||
|
interface PermissionContextType {
|
||||||
|
canModule: (moduleCode: string) => boolean;
|
||||||
|
canAction: (actionCode: string) => boolean;
|
||||||
|
can: (moduleCode: string, actionCode: string) => boolean;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionContext = createContext<PermissionContextType | null>(null);
|
||||||
|
|
||||||
|
export const PermissionProvider = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
const [moduleAccess, setModuleAccess] = useState<ModuleAccessMap>({});
|
||||||
|
const [actionCodes, setActionCodes] = useState<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPermissions = async () => {
|
||||||
|
try {
|
||||||
|
// ✅ SUMBER KEBENARAN: API USER INFO
|
||||||
|
const userRes = await getUserInfo();
|
||||||
|
const userLevelId = userRes?.data?.data?.userLevelId;
|
||||||
|
|
||||||
|
console.log("USER LEVEL ID FROM API:", userLevelId);
|
||||||
|
|
||||||
|
if (!userLevelId) {
|
||||||
|
console.error("userLevelId not found from users/info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [moduleRes, actionRes] = await Promise.all([
|
||||||
|
getUserLevelModuleAccessesByUserLevelId(userLevelId),
|
||||||
|
getUserLevelMenuActionAccesses({
|
||||||
|
userLevelId,
|
||||||
|
canAccess: true,
|
||||||
|
limit: 10000,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("ACTION ACCESS RAW:", actionRes?.data?.data);
|
||||||
|
|
||||||
|
// 🔹 MODULE ACCESS
|
||||||
|
// MODULE ACCESS
|
||||||
|
const moduleMap: Record<string, boolean> = {};
|
||||||
|
moduleRes?.data?.data?.forEach((item: any) => {
|
||||||
|
if (
|
||||||
|
item.module?.code &&
|
||||||
|
item.canAccess === true &&
|
||||||
|
item.isActive !== false
|
||||||
|
) {
|
||||||
|
moduleMap[item.module.code] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACTION ACCESS
|
||||||
|
const actions =
|
||||||
|
actionRes?.data?.data
|
||||||
|
?.filter(
|
||||||
|
(item: any) =>
|
||||||
|
Number(item.userLevelId) === Number(userLevelId) &&
|
||||||
|
item.canAccess === true &&
|
||||||
|
item.isActive === true,
|
||||||
|
)
|
||||||
|
.map((item: any) => item.actionCode) ?? [];
|
||||||
|
|
||||||
|
setModuleAccess(moduleMap);
|
||||||
|
setActionCodes(actions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load permissions", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPermissions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const canModule = (moduleCode: string) => moduleAccess[moduleCode] === true;
|
||||||
|
|
||||||
|
const canAction = (actionCode: string) => actionCodes.includes(actionCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FINAL GUARD
|
||||||
|
* - harus punya module
|
||||||
|
* - harus punya action
|
||||||
|
*/
|
||||||
|
const can = (moduleCode: any, actionCode: any) =>
|
||||||
|
canModule(moduleCode) && canAction(actionCode);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionContext.Provider value={{ canModule, canAction, can, loading }}>
|
||||||
|
{children}
|
||||||
|
</PermissionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePermission = () => {
|
||||||
|
const ctx = useContext(PermissionContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("usePermission must be used inside PermissionProvider");
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -48,6 +48,7 @@ import dynamic from "next/dynamic";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||||
import { listArticleCategories } from "@/service/content";
|
import { listArticleCategories } from "@/service/content";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const videoSchema = z.object({
|
const videoSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -198,7 +199,7 @@ export default function FormVideoDetail() {
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedPublishers((prev) =>
|
setSelectedPublishers((prev) =>
|
||||||
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
|
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -234,6 +235,15 @@ export default function FormVideoDetail() {
|
||||||
|
|
||||||
if (!detail) return <div className="p-10 text-gray-500">Memuat data...</div>;
|
if (!detail) return <div className="p-10 text-gray-500">Memuat data...</div>;
|
||||||
|
|
||||||
|
const isPending = Number(detail?.statusId) === 1;
|
||||||
|
const isApproved = Number(detail?.statusId) === 2;
|
||||||
|
const isRejected = Number(detail?.statusId) === 4;
|
||||||
|
|
||||||
|
const isCreator =
|
||||||
|
Number(detail?.createdById || detail?.uploadedById) === Number(userId);
|
||||||
|
|
||||||
|
const hasApproval = approval != null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
|
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg">
|
||||||
|
|
@ -325,7 +335,7 @@ export default function FormVideoDetail() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 px-3 space-y-2">
|
<div className="mt-3 px-3 space-y-2">
|
||||||
<Label>Preview</Label>
|
<Label>Thumbnail</Label>
|
||||||
<Card className="mt-2 w-fit">
|
<Card className="mt-2 w-fit">
|
||||||
<img
|
<img
|
||||||
src={detail.thumbnailUrl || detail.thumbnailLink}
|
src={detail.thumbnailUrl || detail.thumbnailLink}
|
||||||
|
|
@ -387,7 +397,46 @@ export default function FormVideoDetail() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(Number(detail.needApprovalFromLevel || 0) ==
|
{/* {isPending &&
|
||||||
|
(Number(detail.needApprovalFromLevel || 0) ===
|
||||||
|
Number(userLevelId) ||
|
||||||
|
detail.isPublish === false) &&
|
||||||
|
Number(detail.uploadedById || detail.createdById) !==
|
||||||
|
Number(userId) ? ( */}
|
||||||
|
|
||||||
|
{isPending && (
|
||||||
|
<AccessGuard action="approve">
|
||||||
|
<div className="flex flex-col gap-2 p-3">
|
||||||
|
<Button
|
||||||
|
onClick={() => actionApproval("2")}
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon icon="fa:check" className="mr-3" /> Accept
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => actionApproval("3")}
|
||||||
|
className="bg-orange-400 hover:bg-orange-300"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon icon="fa:comment-o" className="mr-3" /> Revision
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => actionApproval("4")}
|
||||||
|
color="destructive"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon icon="fa:times" className="mr-3" /> Reject
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ) : null} */}
|
||||||
|
|
||||||
|
{/* {(Number(detail.needApprovalFromLevel || 0) ==
|
||||||
Number(userLevelId) ||
|
Number(userLevelId) ||
|
||||||
(detail.isPublish === false && detail.statusId == 1)) &&
|
(detail.isPublish === false && detail.statusId == 1)) &&
|
||||||
Number(detail.uploadedById || detail.createdById) !=
|
Number(detail.uploadedById || detail.createdById) !=
|
||||||
|
|
@ -415,7 +464,7 @@ export default function FormVideoDetail() {
|
||||||
<Icon icon="fa:times" className="mr-3" /> Reject
|
<Icon icon="fa:times" className="mr-3" /> Reject
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null} */}
|
||||||
|
|
||||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
<DialogContent className="max-h-[600px] overflow-y-auto">
|
<DialogContent className="max-h-[600px] overflow-y-auto">
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import AudioPlayer from "@/components/audio-player";
|
import AudioPlayer from "@/components/audio-player";
|
||||||
import { listArticleCategories } from "@/service/content";
|
import { listArticleCategories } from "@/service/content";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -112,7 +113,7 @@ const ViewEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/view-editor");
|
return import("@/components/editor/view-editor");
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function FormAudioDetail() {
|
export default function FormAudioDetail() {
|
||||||
|
|
@ -219,7 +220,7 @@ export default function FormAudioDetail() {
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedPublishers((prev) =>
|
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) {
|
if (id) {
|
||||||
const response = await getArticleDetail(Number(id));
|
const response = await getArticleDetail(Number(id));
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
console.log("detail", details);
|
|
||||||
setFiles(details?.files);
|
setFiles(details?.files);
|
||||||
console.log("ISI FILES:", details?.files);
|
|
||||||
setSelectedCategory(String(details.categories[0].id));
|
setSelectedCategory(String(details.categories[0].id));
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
|
@ -268,12 +267,20 @@ export default function FormAudioDetail() {
|
||||||
|
|
||||||
if (details?.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details?.publishedForObject.map(
|
const publisherIds = details?.publishedForObject.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id,
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
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 filesData = details?.files || [];
|
||||||
// const audioFiles = filesData.filter(
|
// const audioFiles = filesData.filter(
|
||||||
|
|
@ -382,7 +389,7 @@ export default function FormAudioDetail() {
|
||||||
const setupPlacement = (
|
const setupPlacement = (
|
||||||
index: number,
|
index: number,
|
||||||
placement: string,
|
placement: string,
|
||||||
checked: boolean
|
checked: boolean,
|
||||||
) => {
|
) => {
|
||||||
let temp = [...filePlacements];
|
let temp = [...filePlacements];
|
||||||
if (checked) {
|
if (checked) {
|
||||||
|
|
@ -416,7 +423,7 @@ export default function FormAudioDetail() {
|
||||||
type: string,
|
type: string,
|
||||||
url: string,
|
url: string,
|
||||||
names: string,
|
names: string,
|
||||||
format: string
|
format: string,
|
||||||
) => {
|
) => {
|
||||||
console.log("Test 3 :", type, url, names, format);
|
console.log("Test 3 :", type, url, names, format);
|
||||||
setMain({
|
setMain({
|
||||||
|
|
@ -448,6 +455,8 @@ export default function FormAudioDetail() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPending = Number(detail?.statusId) === 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -647,7 +656,7 @@ export default function FormAudioDetail() {
|
||||||
id="terms"
|
id="terms"
|
||||||
value="all"
|
value="all"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"all"
|
"all",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "all", Boolean(e))
|
setupPlacement(index, "all", Boolean(e))
|
||||||
|
|
@ -664,7 +673,7 @@ export default function FormAudioDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"mabes"
|
"mabes",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "mabes", Boolean(e))
|
setupPlacement(index, "mabes", Boolean(e))
|
||||||
|
|
@ -681,7 +690,7 @@ export default function FormAudioDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"polda"
|
"polda",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "polda", Boolean(e))
|
setupPlacement(index, "polda", Boolean(e))
|
||||||
|
|
@ -699,13 +708,13 @@ export default function FormAudioDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"international"
|
"international",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(
|
setupPlacement(
|
||||||
index,
|
index,
|
||||||
"international",
|
"international",
|
||||||
Boolean(e)
|
Boolean(e),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -821,30 +830,34 @@ export default function FormAudioDetail() {
|
||||||
Number(detail?.uploadedById) == Number(userId) ? (
|
Number(detail?.uploadedById) == Number(userId) ? (
|
||||||
""
|
""
|
||||||
) : ( */}
|
) : ( */}
|
||||||
<div className="flex flex-col gap-2 p-3">
|
{isPending && (
|
||||||
<Button
|
<AccessGuard action="approve">
|
||||||
onClick={() => actionApproval("2")}
|
<div className="flex flex-col gap-2 p-3">
|
||||||
color="primary"
|
<Button
|
||||||
type="button"
|
onClick={() => actionApproval("2")}
|
||||||
>
|
color="primary"
|
||||||
<Icon icon="fa:check" className="mr-3" /> Accept
|
type="button"
|
||||||
</Button>
|
>
|
||||||
<Button
|
<Icon icon="fa:check" className="mr-3" /> Accept
|
||||||
onClick={() => actionApproval("3")}
|
</Button>
|
||||||
className="bg-orange-400 hover:bg-orange-300"
|
<Button
|
||||||
type="button"
|
onClick={() => actionApproval("3")}
|
||||||
>
|
className="bg-orange-400 hover:bg-orange-300"
|
||||||
<Icon icon="fa:comment-o" className="mr-3" /> Revision
|
type="button"
|
||||||
</Button>
|
>
|
||||||
<Button
|
<Icon icon="fa:comment-o" className="mr-3" /> Revision
|
||||||
onClick={() => actionApproval("4")}
|
</Button>
|
||||||
color="destructive"
|
<Button
|
||||||
type="button"
|
onClick={() => actionApproval("4")}
|
||||||
>
|
color="destructive"
|
||||||
<Icon icon="fa:times" className="mr-3" />
|
type="button"
|
||||||
Reject
|
>
|
||||||
</Button>
|
<Icon icon="fa:times" className="mr-3" />
|
||||||
</div>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</AccessGuard>
|
||||||
|
)}
|
||||||
{/* )
|
{/* )
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||||
import FileTextPreview from "../file-preview-text";
|
import FileTextPreview from "../file-preview-text";
|
||||||
import FileTextThumbnail from "../file-text-thumbnail";
|
import FileTextThumbnail from "../file-text-thumbnail";
|
||||||
import { listArticleCategories } from "@/service/content";
|
import { listArticleCategories } from "@/service/content";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
type Option = {
|
type Option = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -116,7 +117,7 @@ const ViewEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/view-editor");
|
return import("@/components/editor/view-editor");
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function FormTeksDetail() {
|
export default function FormTeksDetail() {
|
||||||
|
|
@ -195,7 +196,7 @@ export default function FormTeksDetail() {
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedPublishers((prev) =>
|
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) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details.publishedForObject.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id,
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +360,7 @@ export default function FormTeksDetail() {
|
||||||
const setupPlacement = (
|
const setupPlacement = (
|
||||||
index: number,
|
index: number,
|
||||||
placement: string,
|
placement: string,
|
||||||
checked: boolean
|
checked: boolean,
|
||||||
) => {
|
) => {
|
||||||
let temp = [...filePlacements];
|
let temp = [...filePlacements];
|
||||||
if (checked) {
|
if (checked) {
|
||||||
|
|
@ -400,7 +401,7 @@ export default function FormTeksDetail() {
|
||||||
type: string,
|
type: string,
|
||||||
url: string,
|
url: string,
|
||||||
names: string,
|
names: string,
|
||||||
format: string
|
format: string,
|
||||||
) => {
|
) => {
|
||||||
console.log("Test 3 :", type, url, names, format);
|
console.log("Test 3 :", type, url, names, format);
|
||||||
setMain({
|
setMain({
|
||||||
|
|
@ -424,6 +425,8 @@ export default function FormTeksDetail() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPending = Number(detail?.statusId) === 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -528,10 +531,10 @@ export default function FormTeksDetail() {
|
||||||
</Card>
|
</Card>
|
||||||
<div className="w-full lg:w-4/12 m-2">
|
<div className="w-full lg:w-4/12 m-2">
|
||||||
<Card className="pb-3">
|
<Card className="pb-3">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<Label>Creator</Label>
|
<Label>Creator</Label>
|
||||||
<Input value={detail.createdByName} disabled />
|
<Input value={detail.createdByName} disabled />
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="mt-3 px-3">
|
{/* <div className="mt-3 px-3">
|
||||||
<Label>Pratinjau Gambar Utama</Label>
|
<Label>Pratinjau Gambar Utama</Label>
|
||||||
<Card className="mt-2">
|
<Card className="mt-2">
|
||||||
|
|
@ -647,7 +650,7 @@ export default function FormTeksDetail() {
|
||||||
id="terms"
|
id="terms"
|
||||||
value="all"
|
value="all"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"all"
|
"all",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "all", Boolean(e))
|
setupPlacement(index, "all", Boolean(e))
|
||||||
|
|
@ -664,7 +667,7 @@ export default function FormTeksDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"mabes"
|
"mabes",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "mabes", Boolean(e))
|
setupPlacement(index, "mabes", Boolean(e))
|
||||||
|
|
@ -681,7 +684,7 @@ export default function FormTeksDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"polda"
|
"polda",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "polda", Boolean(e))
|
setupPlacement(index, "polda", Boolean(e))
|
||||||
|
|
@ -699,13 +702,13 @@ export default function FormTeksDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"international"
|
"international",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(
|
setupPlacement(
|
||||||
index,
|
index,
|
||||||
"international",
|
"international",
|
||||||
Boolean(e)
|
Boolean(e),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -817,10 +820,17 @@ export default function FormTeksDetail() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Card>
|
</Card>
|
||||||
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
{/* {isPending &&
|
||||||
Number(detail?.uploadedById) == Number(userId) ? (
|
(Number(detail?.needApprovalFromLevel || 0) ===
|
||||||
|
Number(userLevelId) ||
|
||||||
|
(detail?.isInternationalMedia === true &&
|
||||||
|
detail?.isForwardFromNational === true)) ? (
|
||||||
|
Number(detail?.createdById || detail?.uploadedById) ==
|
||||||
|
Number(userId) ? (
|
||||||
""
|
""
|
||||||
) : ( */}
|
) : ( */}
|
||||||
|
{isPending && (
|
||||||
|
<AccessGuard action="approve">
|
||||||
<div className="flex flex-col gap-2 p-3">
|
<div className="flex flex-col gap-2 p-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("2")}
|
onClick={() => actionApproval("2")}
|
||||||
|
|
@ -829,6 +839,7 @@ export default function FormTeksDetail() {
|
||||||
>
|
>
|
||||||
<Icon icon="fa:check" className="mr-3" /> Accept
|
<Icon icon="fa:check" className="mr-3" /> Accept
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("3")}
|
onClick={() => actionApproval("3")}
|
||||||
className="bg-orange-400 hover:bg-orange-300"
|
className="bg-orange-400 hover:bg-orange-300"
|
||||||
|
|
@ -836,6 +847,7 @@ export default function FormTeksDetail() {
|
||||||
>
|
>
|
||||||
<Icon icon="fa:comment-o" className="mr-3" /> Revision
|
<Icon icon="fa:comment-o" className="mr-3" /> Revision
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("4")}
|
onClick={() => actionApproval("4")}
|
||||||
color="destructive"
|
color="destructive"
|
||||||
|
|
@ -845,7 +857,9 @@ export default function FormTeksDetail() {
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{/* )
|
</AccessGuard>
|
||||||
|
)}
|
||||||
|
{/* )
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)} */}
|
)} */}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ import {
|
||||||
getDataApprovalByMediaUpload,
|
getDataApprovalByMediaUpload,
|
||||||
} from "@/service/curated-content/curated-content";
|
} from "@/service/curated-content/curated-content";
|
||||||
import { UnitMapping } from "../unit-mapping";
|
import { UnitMapping } from "../unit-mapping";
|
||||||
|
import { AccessGuard } from "@/components/access-guard";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -161,7 +162,7 @@ const ViewEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/view-editor");
|
return import("@/components/editor/view-editor");
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function FormImageDetail() {
|
export default function FormImageDetail() {
|
||||||
|
|
@ -171,10 +172,8 @@ export default function FormImageDetail() {
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = getCookiesDecrypt("ulie");
|
||||||
const userLevelName = Cookies.get("state");
|
const userLevelName = Cookies.get("state");
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
console.log("LALALALA", userLevelName);
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
console.log("IDIDIDIDI", id);
|
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
type ImageSchema = z.infer<typeof imageSchema>;
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
|
|
@ -233,7 +232,7 @@ export default function FormImageDetail() {
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedPublishers((prev) =>
|
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") {
|
if (scheduleId && scheduleType === "3") {
|
||||||
const findCategory = resCategory?.find((o) =>
|
const findCategory = resCategory?.find((o) =>
|
||||||
o.name.toLowerCase().includes("pers rilis")
|
o.name.toLowerCase().includes("pers rilis"),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (findCategory) {
|
if (findCategory) {
|
||||||
|
|
@ -365,7 +364,7 @@ export default function FormImageDetail() {
|
||||||
setSelectedTarget(String(mappedDetail.categoryId));
|
setSelectedTarget(String(mappedDetail.categoryId));
|
||||||
|
|
||||||
const fileUrls = (mappedFiles || []).map(
|
const fileUrls = (mappedFiles || []).map(
|
||||||
(file) => file.thumbnailFileUrl || file.url || "default-image.jpg"
|
(file) => file.thumbnailFileUrl || file.url || "default-image.jpg",
|
||||||
);
|
);
|
||||||
|
|
||||||
setDetailThumb(fileUrls);
|
setDetailThumb(fileUrls);
|
||||||
|
|
@ -478,7 +477,7 @@ export default function FormImageDetail() {
|
||||||
const setupPlacement = (
|
const setupPlacement = (
|
||||||
index: number,
|
index: number,
|
||||||
placement: string,
|
placement: string,
|
||||||
checked: boolean
|
checked: boolean,
|
||||||
) => {
|
) => {
|
||||||
let temp = [...filePlacements];
|
let temp = [...filePlacements];
|
||||||
if (checked) {
|
if (checked) {
|
||||||
|
|
@ -519,7 +518,7 @@ export default function FormImageDetail() {
|
||||||
type: string,
|
type: string,
|
||||||
url: string,
|
url: string,
|
||||||
names: string,
|
names: string,
|
||||||
format: string
|
format: string,
|
||||||
) => {
|
) => {
|
||||||
console.log("Test 3 :", type, url, names, format);
|
console.log("Test 3 :", type, url, names, format);
|
||||||
setMain({
|
setMain({
|
||||||
|
|
@ -566,6 +565,15 @@ export default function FormImageDetail() {
|
||||||
console.log("portrai", portraitMap);
|
console.log("portrai", portraitMap);
|
||||||
}, [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 (
|
return (
|
||||||
<form>
|
<form>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -615,14 +623,14 @@ export default function FormImageDetail() {
|
||||||
!categories?.find(
|
!categories?.find(
|
||||||
(cat) =>
|
(cat) =>
|
||||||
String(cat.id) ===
|
String(cat.id) ===
|
||||||
String(detail.categoryId || detail?.category?.id)
|
String(detail.categoryId || detail?.category?.id),
|
||||||
) && (
|
) && (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={String(
|
key={String(
|
||||||
detail.categoryId || detail?.category?.id
|
detail.categoryId || detail?.category?.id,
|
||||||
)}
|
)}
|
||||||
value={String(
|
value={String(
|
||||||
detail.categoryId || detail?.category?.id
|
detail.categoryId || detail?.category?.id,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{detail.categoryName || detail?.category?.name}
|
{detail.categoryName || detail?.category?.name}
|
||||||
|
|
@ -915,7 +923,7 @@ export default function FormImageDetail() {
|
||||||
id="terms"
|
id="terms"
|
||||||
value="all"
|
value="all"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"all"
|
"all",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(index, "all", Boolean(e))
|
setupPlacement(index, "all", Boolean(e))
|
||||||
|
|
@ -932,13 +940,13 @@ export default function FormImageDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"mabes"
|
"mabes",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(
|
setupPlacement(
|
||||||
index,
|
index,
|
||||||
"mabes",
|
"mabes",
|
||||||
Boolean(e)
|
Boolean(e),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -953,13 +961,13 @@ export default function FormImageDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"polda"
|
"polda",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(
|
setupPlacement(
|
||||||
index,
|
index,
|
||||||
"polda",
|
"polda",
|
||||||
Boolean(e)
|
Boolean(e),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -984,13 +992,13 @@ export default function FormImageDetail() {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
checked={filePlacements[index]?.includes(
|
checked={filePlacements[index]?.includes(
|
||||||
"international"
|
"international",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
setupPlacement(
|
setupPlacement(
|
||||||
index,
|
index,
|
||||||
"international",
|
"international",
|
||||||
Boolean(e)
|
Boolean(e),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -1111,15 +1119,22 @@ export default function FormImageDetail() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Card>
|
</Card>
|
||||||
{Number(detail?.needApprovalFromLevel || 0) ==
|
{/* {Number(detail?.needApprovalFromLevel || 0) ==
|
||||||
Number(userLevelId) ||
|
Number(userLevelId) ||
|
||||||
(detail?.isInternationalMedia == true &&
|
(detail?.isInternationalMedia == true &&
|
||||||
detail?.isForwardFromNational == 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(detail?.createdById || detail?.uploadedById) ==
|
||||||
Number(userId) ? (
|
Number(userId) ? (
|
||||||
""
|
""
|
||||||
) : (
|
) : ( */}
|
||||||
|
{isPending && (
|
||||||
|
<AccessGuard action="approve">
|
||||||
<div className="flex flex-col gap-2 p-3">
|
<div className="flex flex-col gap-2 p-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("2")}
|
onClick={() => actionApproval("2")}
|
||||||
|
|
@ -1145,10 +1160,12 @@ export default function FormImageDetail() {
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
</AccessGuard>
|
||||||
|
)}
|
||||||
|
{/* )
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ export default function SignUp() {
|
||||||
const [whatsapp, setWhatsapp] = useState("");
|
const [whatsapp, setWhatsapp] = useState("");
|
||||||
const [namaTenant, setNamaTenant] = useState("");
|
const [namaTenant, setNamaTenant] = useState("");
|
||||||
const [tenantPassword, setTenantPassword] = useState("");
|
const [tenantPassword, setTenantPassword] = useState("");
|
||||||
|
const [tenantUsername, setTenantUsername] = useState("");
|
||||||
const [confirmTenantPassword, setConfirmTenantPassword] = useState("");
|
const [confirmTenantPassword, setConfirmTenantPassword] = useState("");
|
||||||
const [firstNameKontributor, setFirstNameKontributor] = useState("");
|
const [firstNameKontributor, setFirstNameKontributor] = useState("");
|
||||||
const [lastNameKontributor, setLastNameKontributor] = useState("");
|
const [lastNameKontributor, setLastNameKontributor] = useState("");
|
||||||
|
|
@ -119,7 +120,11 @@ export default function SignUp() {
|
||||||
if (value && nextInput) nextInput.focus();
|
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 validateEmail = (email: string) => {
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
return emailRegex.test(email);
|
return emailRegex.test(email);
|
||||||
|
|
@ -134,6 +139,11 @@ export default function SignUp() {
|
||||||
return password.length >= 8;
|
return password.length >= 8;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateUsername = (username: string) => {
|
||||||
|
const usernameRegex = /^[a-z0-9_-]+$/;
|
||||||
|
return usernameRegex.test(username);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreateUserKontributor = async (e: React.FormEvent) => {
|
const handleCreateUserKontributor = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|
@ -141,7 +151,7 @@ export default function SignUp() {
|
||||||
MySwal.fire(
|
MySwal.fire(
|
||||||
"Peringatan",
|
"Peringatan",
|
||||||
"Nama depan dan belakang wajib diisi",
|
"Nama depan dan belakang wajib diisi",
|
||||||
"warning"
|
"warning",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -260,12 +270,29 @@ export default function SignUp() {
|
||||||
const validateTenantForm = () => {
|
const validateTenantForm = () => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
|
// if (!firstName.trim()) {
|
||||||
|
// errors.firstName = "First name is required";
|
||||||
|
// }
|
||||||
if (!firstName.trim()) {
|
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()) {
|
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()) {
|
if (!email.trim()) {
|
||||||
|
|
@ -303,9 +330,7 @@ export default function SignUp() {
|
||||||
const handleTenantRegistration = async (e: React.FormEvent) => {
|
const handleTenantRegistration = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!validateTenantForm()) {
|
if (!validateTenantForm()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setFormErrors({});
|
setFormErrors({});
|
||||||
|
|
@ -314,43 +339,75 @@ export default function SignUp() {
|
||||||
const registrationData = {
|
const registrationData = {
|
||||||
adminUser: {
|
adminUser: {
|
||||||
address: "Jakarta",
|
address: "Jakarta",
|
||||||
email: email,
|
email: email.trim(),
|
||||||
fullname: `${firstName} ${lastName}`,
|
fullname: `${firstName.trim()} ${lastName.trim()}`,
|
||||||
password: tenantPassword,
|
password: tenantPassword,
|
||||||
phoneNumber: whatsapp,
|
phoneNumber: whatsapp.trim(),
|
||||||
username: `${firstName}-${lastName}`,
|
username: tenantUsername.trim(),
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
clientType: "sub_client",
|
clientType: "sub_client",
|
||||||
name: namaTenant,
|
name: namaTenant.trim(),
|
||||||
parentClientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead",
|
parentClientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await registerTenant(registrationData);
|
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({
|
MySwal.fire({
|
||||||
title: "Registration Failed",
|
title: "Registrasi Gagal",
|
||||||
text: response.message || "An error occurred during registration",
|
text: backendMessage || "Tenant gagal dibuat",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
});
|
});
|
||||||
} else {
|
return;
|
||||||
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);
|
/**
|
||||||
|
* ✅ 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({
|
MySwal.fire({
|
||||||
title: "Registration Failed",
|
title: "Terjadi Kesalahan",
|
||||||
text: "An unexpected error occurred. Please try again.",
|
text: "Terjadi kesalahan server. Silakan coba lagi.",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK",
|
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
|
// Generate username otomatis dari nama lengkap
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (fullname.trim()) {
|
if (fullname.trim()) {
|
||||||
|
|
@ -685,6 +802,9 @@ export default function SignUp() {
|
||||||
formErrors.firstName ? "border-red-500" : ""
|
formErrors.firstName ? "border-red-500" : ""
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<p className="text-[10px] text-gray-500 mt-1">
|
||||||
|
Hanya huruf dan spasi.
|
||||||
|
</p>
|
||||||
{formErrors.firstName && (
|
{formErrors.firstName && (
|
||||||
<p className="text-red-500 text-xs mt-1">
|
<p className="text-red-500 text-xs mt-1">
|
||||||
{formErrors.firstName}
|
{formErrors.firstName}
|
||||||
|
|
@ -710,6 +830,32 @@ export default function SignUp() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="Username"
|
||||||
|
value={tenantUsername}
|
||||||
|
onChange={(e) =>
|
||||||
|
setTenantUsername(e.target.value.toLowerCase())
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
formErrors.tenantUsername ? "border-red-500" : ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
|
Username harus huruf kecil, tanpa spasi. Contoh:{" "}
|
||||||
|
<b>netidhub-admin</b>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{formErrors.tenantUsername && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">
|
||||||
|
{formErrors.tenantUsername}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
|
|
|
||||||
|
|
@ -8,29 +8,42 @@ import { Card } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
||||||
import { close } from "@/config/swal";
|
import { close } from "@/config/swal";
|
||||||
import {
|
import {
|
||||||
createUser,
|
createUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
getUserDetail,
|
getUserDetail,
|
||||||
CreateUserRequest,
|
CreateUserRequest,
|
||||||
UpdateUserRequest
|
UpdateUserRequest,
|
||||||
} from "@/service/management-user/management-user";
|
} from "@/service/management-user/management-user";
|
||||||
import { getInfoProfile } from "@/service/auth";
|
import { getInfoProfile } from "@/service/auth";
|
||||||
import { getUserLevels } from "@/service/approval-workflows";
|
import { getUserLevels } from "@/service/approval-workflows";
|
||||||
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
|
||||||
const createUserSchema = z.object({
|
const createUserSchema = z.object({
|
||||||
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
|
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
|
||||||
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
|
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
|
||||||
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
|
dateOfBirth: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Date of Birth wajib diisi" }),
|
||||||
email: z.string().email({ message: "Email tidak valid" }),
|
email: z.string().email({ message: "Email tidak valid" }),
|
||||||
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
|
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
|
||||||
password: z.string().min(6, { message: "Password minimal 6 karakter" }),
|
password: z.string().min(6, { message: "Password minimal 6 karakter" }),
|
||||||
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
|
phoneNumber: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Phone Number wajib diisi" }),
|
||||||
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
|
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
|
||||||
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
|
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
|
||||||
});
|
});
|
||||||
|
|
@ -38,11 +51,20 @@ const createUserSchema = z.object({
|
||||||
const editUserSchema = z.object({
|
const editUserSchema = z.object({
|
||||||
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
|
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
|
||||||
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
|
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
|
||||||
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
|
dateOfBirth: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Date of Birth wajib diisi" }),
|
||||||
email: z.string().email({ message: "Email tidak valid" }),
|
email: z.string().email({ message: "Email tidak valid" }),
|
||||||
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
|
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
|
||||||
password: z.string().min(6, { message: "Password minimal 6 karakter" }).optional(),
|
password: z
|
||||||
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
|
.string()
|
||||||
|
.min(6, { message: "Password minimal 6 karakter" })
|
||||||
|
.optional(),
|
||||||
|
phoneNumber: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Phone Number wajib diisi" }),
|
||||||
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
|
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
|
||||||
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
|
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
|
||||||
});
|
});
|
||||||
|
|
@ -67,8 +89,11 @@ export default function UserForm({
|
||||||
}: UserFormProps) {
|
}: UserFormProps) {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const [loadingData, setLoadingData] = useState(false);
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
|
const [userLevels, setUserLevels] = useState<{ id: number; name: string }[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
const [currentClientId, setCurrentClientId] = useState<string>("");
|
const [currentClientId, setCurrentClientId] = useState<string>("");
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -76,7 +101,9 @@ export default function UserForm({
|
||||||
setValue,
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<UserSchema>({
|
} = useForm<UserSchema>({
|
||||||
resolver: zodResolver(mode === "create" ? createUserSchema : editUserSchema),
|
resolver: zodResolver(
|
||||||
|
mode === "create" ? createUserSchema : editUserSchema,
|
||||||
|
),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
address: "",
|
address: "",
|
||||||
clientId: "",
|
clientId: "",
|
||||||
|
|
@ -106,7 +133,8 @@ export default function UserForm({
|
||||||
// Get clientId from current user info
|
// Get clientId from current user info
|
||||||
if (!userInfoResponse?.error && userInfoResponse?.data?.data) {
|
if (!userInfoResponse?.error && userInfoResponse?.data?.data) {
|
||||||
const userInfo = 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);
|
setCurrentClientId(clientId);
|
||||||
setValue("clientId", clientId);
|
setValue("clientId", clientId);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -132,7 +160,10 @@ export default function UserForm({
|
||||||
setValue("username", user.username || "");
|
setValue("username", user.username || "");
|
||||||
// Don't set password for edit mode
|
// Don't set password for edit mode
|
||||||
} else {
|
} else {
|
||||||
console.error("Gagal mengambil detail user:", userResponse?.message || "Unknown error");
|
console.error(
|
||||||
|
"Gagal mengambil detail user:",
|
||||||
|
userResponse?.message || "Unknown error",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading user detail:", error);
|
console.error("Error loading user detail:", error);
|
||||||
|
|
@ -161,7 +192,7 @@ export default function UserForm({
|
||||||
password: data.password || "",
|
password: data.password || "",
|
||||||
phoneNumber: data.phoneNumber,
|
phoneNumber: data.phoneNumber,
|
||||||
userLevelId: data.userLevelId,
|
userLevelId: data.userLevelId,
|
||||||
userRoleId: 3, // Hardcoded as per requirement
|
userRoleId: 3,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -177,18 +208,25 @@ export default function UserForm({
|
||||||
close();
|
close();
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
errorAutoClose(response.message || `Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
|
errorAutoClose(
|
||||||
|
response.message ||
|
||||||
|
`Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
successAutoClose(`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`);
|
successAutoClose(
|
||||||
|
`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`,
|
||||||
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (onSuccess) onSuccess();
|
if (onSuccess) onSuccess();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
close();
|
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);
|
console.error("User operation error:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -261,6 +299,41 @@ export default function UserForm({
|
||||||
|
|
||||||
{/* Password - Only show for create mode */}
|
{/* Password - Only show for create mode */}
|
||||||
{mode === "create" && (
|
{mode === "create" && (
|
||||||
|
<div>
|
||||||
|
<Label>Password</Label>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="Masukkan password"
|
||||||
|
className="pr-10"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword((prev) => !prev)}
|
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{errors.password && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">
|
||||||
|
{errors.password.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* {mode === "create" && (
|
||||||
<div>
|
<div>
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -274,7 +347,7 @@ export default function UserForm({
|
||||||
<p className="text-red-500 text-sm">{errors.password.message}</p>
|
<p className="text-red-500 text-sm">{errors.password.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{/* Phone Number */}
|
{/* Phone Number */}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -287,7 +360,9 @@ export default function UserForm({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.phoneNumber && (
|
{errors.phoneNumber && (
|
||||||
<p className="text-red-500 text-sm">{errors.phoneNumber.message}</p>
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.phoneNumber.message}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -312,17 +387,17 @@ export default function UserForm({
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="dateOfBirth"
|
name="dateOfBirth"
|
||||||
render={({ field }) => (
|
render={({ field }) => <Input {...field} type="date" />}
|
||||||
<Input {...field} type="date" />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
{errors.dateOfBirth && (
|
{errors.dateOfBirth && (
|
||||||
<p className="text-red-500 text-sm">{errors.dateOfBirth.message}</p>
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.dateOfBirth.message}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Client ID */}
|
{/* Client ID */}
|
||||||
<div>
|
{/* <div>
|
||||||
<Label>Client ID</Label>
|
<Label>Client ID</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -342,7 +417,7 @@ export default function UserForm({
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
Client ID diambil dari profil pengguna yang sedang login
|
Client ID diambil dari profil pengguna yang sedang login
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* User Level */}
|
{/* User Level */}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -351,7 +426,10 @@ export default function UserForm({
|
||||||
control={control}
|
control={control}
|
||||||
name="userLevelId"
|
name="userLevelId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select onValueChange={(value) => field.onChange(Number(value))} value={field.value?.toString() || ""}>
|
<Select
|
||||||
|
onValueChange={(value) => field.onChange(Number(value))}
|
||||||
|
value={field.value?.toString() || ""}
|
||||||
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Pilih user level" />
|
<SelectValue placeholder="Pilih user level" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -372,16 +450,16 @@ export default function UserForm({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.userLevelId && (
|
{errors.userLevelId && (
|
||||||
<p className="text-red-500 text-sm">{errors.userLevelId.message}</p>
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.userLevelId.message}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<div className="mt-6 flex justify-end gap-3">
|
<div className="mt-6 flex justify-end gap-3">
|
||||||
<Button type="submit">
|
<Button type="submit">{mode === "edit" ? "Update" : "Create"}</Button>
|
||||||
{mode === "edit" ? "Update" : "Create"}
|
|
||||||
</Button>
|
|
||||||
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
|
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ const UserInternalTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="bg-white dark:bg-slate-200">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -396,7 +396,7 @@ const UserInternalTable = () => {
|
||||||
totalData={totalData}
|
totalData={totalData}
|
||||||
totalPage={totalPage}
|
totalPage={totalPage}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,28 +49,59 @@ export async function getUserLevelMenuActionAccesses(params?: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}) {
|
}) {
|
||||||
const queryParams = new URLSearchParams();
|
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?.menuId) queryParams.append("menu_id", params.menuId.toString());
|
||||||
if (params?.actionCode) queryParams.append("action_code", params.actionCode);
|
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?.page) queryParams.append("page", params.page.toString());
|
||||||
if (params?.limit) queryParams.append("limit", params.limit.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);
|
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) {
|
export async function getUserLevelMenuActionAccessById(id: number) {
|
||||||
const url = `user-level-menu-action-accesses/${id}`;
|
const url = `user-level-menu-action-accesses/${id}`;
|
||||||
return httpGetInterceptor(url);
|
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}`;
|
const url = `user-level-menu-action-accesses/user-level/${userLevelId}`;
|
||||||
return httpGetInterceptor(url);
|
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}`;
|
const url = `user-level-menu-action-accesses/user-level/${userLevelId}/menu/${menuId}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
@ -80,22 +111,33 @@ export async function getUserLevelMenuActionAccessesByMenuId(menuId: number) {
|
||||||
return httpGetInterceptor(url);
|
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}`;
|
const url = `user-level-menu-action-accesses/check/${userLevelId}/${menuId}/${actionCode}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUserLevelMenuActionAccess(data: UserLevelMenuActionAccessCreateRequest) {
|
export async function createUserLevelMenuActionAccess(
|
||||||
|
data: UserLevelMenuActionAccessCreateRequest,
|
||||||
|
) {
|
||||||
const url = "user-level-menu-action-accesses";
|
const url = "user-level-menu-action-accesses";
|
||||||
return httpPostInterceptor(url, data);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUserLevelMenuActionAccessesBatch(data: UserLevelMenuActionAccessBatchCreateRequest) {
|
export async function createUserLevelMenuActionAccessesBatch(
|
||||||
|
data: UserLevelMenuActionAccessBatchCreateRequest,
|
||||||
|
) {
|
||||||
const url = "user-level-menu-action-accesses/batch";
|
const url = "user-level-menu-action-accesses/batch";
|
||||||
return httpPostInterceptor(url, data);
|
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}`;
|
const url = `user-level-menu-action-accesses/${id}`;
|
||||||
return httpPutInterceptor(url, data);
|
return httpPutInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
@ -104,4 +146,3 @@ export async function deleteUserLevelMenuActionAccess(id: number) {
|
||||||
const url = `user-level-menu-action-accesses/${id}`;
|
const url = `user-level-menu-action-accesses/${id}`;
|
||||||
return httpDeleteInterceptor(url);
|
return httpDeleteInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { httpGetInterceptor } from "./http-config/http-interceptor-service";
|
||||||
|
|
||||||
|
export function getUserInfo() {
|
||||||
|
return httpGetInterceptor("users/info");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue