diff --git a/src/app/globals.css b/src/app/globals.css
index 386e7620..79af241b 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,5 +1,6 @@
@import 'tailwindcss';
@plugin "daisyui";
+@import '../styles/daisyui.css';
:root {
--color-primary: #1f74bf;
diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx
new file mode 100644
index 00000000..02f86dca
--- /dev/null
+++ b/src/components/Tooltip.tsx
@@ -0,0 +1,60 @@
+import { ReactNode } from 'react';
+
+import { cn } from '@/lib/helper';
+import { Color } from '@/types/theme';
+
+interface TooltipProps {
+ children?: ReactNode;
+ content?: ReactNode;
+ className?: {
+ wrapper?: string;
+ content?: string;
+ };
+ open?: boolean;
+ color?: Color;
+ position?: 'top' | 'bottom' | 'left' | 'right';
+}
+
+const Tooltip = ({
+ children,
+ content,
+ className,
+ open,
+ color,
+ position,
+}: TooltipProps) => {
+ const tooltipBaseClassName = cn('tooltip', {
+ 'tooltip-open': typeof open === 'boolean' && open,
+
+ 'tooltip-top': position === 'top',
+ 'tooltip-bottom': position === 'bottom',
+ 'tooltip-left': position === 'left',
+ 'tooltip-right': position === 'right',
+
+ 'tooltip-primary': color === 'primary',
+ 'tooltip-secondary': color === 'secondary',
+ 'tooltip-accent': color === 'accent',
+ 'tooltip-neutral': color === 'neutral',
+ 'tooltip-info': color === 'info',
+ 'tooltip-success': color === 'success',
+ 'tooltip-warning': color === 'warning',
+ 'tooltip-error': color === 'error',
+ });
+ return (
+
+
+ {content}
+
+
+ {children}
+
+ );
+};
+
+export default Tooltip;
diff --git a/src/components/pages/ApprovalSteps.tsx b/src/components/pages/ApprovalSteps.tsx
new file mode 100644
index 00000000..4022e254
--- /dev/null
+++ b/src/components/pages/ApprovalSteps.tsx
@@ -0,0 +1,64 @@
+import { Icon } from '@iconify/react';
+import Steps from '@/components/steps/Steps';
+import StepItem from '@/components/steps/StepItem';
+import Tooltip from '@/components/Tooltip';
+
+import { formatDate } from '@/lib/helper';
+import { ApprovalsLine } from '@/types/api/api-general';
+
+interface ApprovalStepsProps {
+ approvals: ApprovalsLine;
+}
+
+const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
+ return (
+
+ {approvals.map((approval, idx) => {
+ const stepItemColor =
+ approval.status === 'approved'
+ ? 'success'
+ : approval.status === 'rejected'
+ ? 'error'
+ : undefined;
+
+ const stepItemIcon =
+ approval.status === 'approved'
+ ? 'material-symbols:check-rounded'
+ : approval.status === 'rejected'
+ ? 'material-symbols:close-rounded'
+ : 'bxs:hourglass';
+
+ return (
+
+ {formatDate(approval.date, 'YYYY-MM-DD')}
+ Oleh: {approval.action_by}
+ Catatan: {approval.notes}
+
+ }
+ >
+
+
+ )
+ }
+ >
+ {approval.role}
+
+ );
+ })}
+
+ );
+};
+
+export default ApprovalSteps;
diff --git a/src/components/steps/StepItem.tsx b/src/components/steps/StepItem.tsx
new file mode 100644
index 00000000..85ec4f3e
--- /dev/null
+++ b/src/components/steps/StepItem.tsx
@@ -0,0 +1,34 @@
+import { ReactNode } from 'react';
+
+import { cn } from '@/lib/helper';
+import { Color } from '@/types/theme';
+
+interface StepItemProps {
+ children?: ReactNode;
+ icon?: ReactNode;
+ className?: string;
+ color?: Color;
+}
+
+const StepItem = ({ children, icon, className, color }: StepItemProps) => {
+ const stepItemBaseClassName = cn('step', {
+ 'step-primary': color === 'primary',
+ 'step-secondary': color === 'secondary',
+ 'step-accent': color === 'accent',
+ 'step-neutral': color === 'neutral',
+ 'step-info': color === 'info',
+ 'step-success': color === 'success',
+ 'step-warning': color === 'warning',
+ 'step-error': color === 'error',
+ });
+
+ return (
+
+ {icon}
+
+ {children}
+
+ );
+};
+
+export default StepItem;
diff --git a/src/components/steps/Steps.tsx b/src/components/steps/Steps.tsx
new file mode 100644
index 00000000..29d307e1
--- /dev/null
+++ b/src/components/steps/Steps.tsx
@@ -0,0 +1,23 @@
+import { ReactNode } from 'react';
+import { cn } from '@/lib/helper';
+
+interface StepsProps {
+ children?: ReactNode;
+ className?: string;
+ direction?: 'horizontal' | 'vertical';
+}
+
+const Steps = ({ children, className, direction }: StepsProps) => {
+ const stepsBaseClassName = cn('steps gap-2', {
+ 'steps-horizontal': direction === 'horizontal',
+ 'steps-vertical': direction === 'vertical',
+ });
+
+ return (
+
+ );
+};
+
+export default Steps;
diff --git a/src/services/hooks/useAuth.tsx b/src/services/hooks/useAuth.tsx
index 86bf43ed..79fa8981 100644
--- a/src/services/hooks/useAuth.tsx
+++ b/src/services/hooks/useAuth.tsx
@@ -6,22 +6,43 @@ type AuthStore = {
isLoadingUser?: boolean;
setUser: (newUserData?: UserWithRoles) => void;
setIsLoadingUser: (isLoading?: boolean) => void;
+ permissionCheck: (permissionName: string) => boolean;
};
-const useAuthStore = create()((set) => ({
+const useAuthStore = create()((set, get) => ({
user: undefined,
isLoadingUser: false,
setUser: (newUserData) => set({ user: newUserData }),
setIsLoadingUser: (isLoading) => set({ isLoadingUser: Boolean(isLoading) }),
+
+ permissionCheck: (name) => {
+ const { user, isLoadingUser } = get();
+
+ if (!isLoadingUser && user) {
+ const isAllowed = user.roles.some((role) => {
+ const isPermissionNameAllowed = role.permissions.some(
+ (permission) => permission.name === name
+ );
+
+ return isPermissionNameAllowed;
+ });
+
+ return isAllowed;
+ }
+
+ return false;
+ },
}));
export const useAuth = () => {
- const { user, setUser, isLoadingUser, setIsLoadingUser } = useAuthStore();
+ const { user, setUser, isLoadingUser, setIsLoadingUser, permissionCheck } =
+ useAuthStore();
return {
user,
setUser,
isLoadingUser,
setIsLoadingUser,
+ permissionCheck,
};
};
diff --git a/src/styles/daisyui.css b/src/styles/daisyui.css
new file mode 100644
index 00000000..9a148fb4
--- /dev/null
+++ b/src/styles/daisyui.css
@@ -0,0 +1,11 @@
+@layer utilities {
+ .step.step-success::before {
+ --step-bg: var(--color-success);
+ --step-fg: var(--color-success-content);
+ }
+
+ .step.step-error::before {
+ --step-bg: var(--color-error);
+ --step-fg: var(--color-error-content);
+ }
+}
diff --git a/src/types/api/api-general.d.ts b/src/types/api/api-general.d.ts
index 6a3fc6be..c118b5a4 100644
--- a/src/types/api/api-general.d.ts
+++ b/src/types/api/api-general.d.ts
@@ -24,6 +24,36 @@ export type LogoutResponse = BaseApiResponse;
export type GetMeResponse = BaseApiResponse;
+export type Client = {
+ id: number;
+ name: stirng;
+ alias: string;
+ created_at: string;
+ updated_at: string;
+};
+
+export type Permission = {
+ id: number;
+ name: string;
+ action: string;
+ client: Omit;
+ created_at: string;
+ updated_at: string;
+};
+
+export type Role = {
+ id: number;
+ key: string;
+ name: string;
+ client: Omit;
+ created_at: string;
+ updated_at: string;
+};
+
+export type RoleWithPermissions = Omit & {
+ permissions: Omit[];
+};
+
export type User = {
id: number;
email: string;
@@ -66,3 +96,11 @@ export type flags =
| 'STARTER'
| 'FINISHER'
| 'OVK';
+
+export type ApprovalsLine = {
+ action_by?: string;
+ date?: string;
+ notes?: string;
+ role?: string;
+ status: 'approved' | 'rejected' | 'waiting';
+}[];