diff --git a/src/app/production/uniformity/add/page.tsx b/src/app/production/uniformity/add/page.tsx
new file mode 100644
index 00000000..136aab5d
--- /dev/null
+++ b/src/app/production/uniformity/add/page.tsx
@@ -0,0 +1,7 @@
+import UniformityForm from '@/components/pages/production/uniformity/form/UniformityForm';
+
+const AddUniformity = () => {
+ return ;
+};
+
+export default AddUniformity;
diff --git a/src/app/production/uniformity/detail/page.tsx b/src/app/production/uniformity/detail/page.tsx
new file mode 100644
index 00000000..bf1458ef
--- /dev/null
+++ b/src/app/production/uniformity/detail/page.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import UniformityDetail from '@/components/pages/production/uniformity/detail/UniformityDetail';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { UniformityApi } from '@/services/api/uniformity';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const UniformityDetailPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const uniformityId = searchParams.get('uniformityId');
+
+ const { data: uniformity, isLoading: isLoadingUniformity } = useSWR(
+ uniformityId,
+ (id: string) => UniformityApi.getUniformityDetail(parseInt(id))
+ );
+
+ if (!uniformityId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingUniformity && (!uniformity || isResponseError(uniformity))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingUniformity && (
+
+
+
+ )}
+ {isResponseSuccess(uniformity) && (
+
+ )}
+
+ );
+};
+
+export default UniformityDetailPage;
diff --git a/src/app/production/uniformity/layout.tsx b/src/app/production/uniformity/layout.tsx
new file mode 100644
index 00000000..511aa0a1
--- /dev/null
+++ b/src/app/production/uniformity/layout.tsx
@@ -0,0 +1,10 @@
+import { ReactNode } from 'react';
+import UniformityPageWrapper from '@/components/pages/production/uniformity/UniformityPageWrapper';
+
+export default function UniformityLayout({
+ children,
+}: {
+ children: ReactNode;
+}) {
+ return {children};
+}
diff --git a/src/app/production/uniformity/page.tsx b/src/app/production/uniformity/page.tsx
new file mode 100644
index 00000000..841a7507
--- /dev/null
+++ b/src/app/production/uniformity/page.tsx
@@ -0,0 +1,7 @@
+import UniformityTable from '@/components/pages/production/uniformity/UniformityTable';
+
+const Uniformity = () => {
+ return ;
+};
+
+export default Uniformity;
diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx
index 5dc5022d..821aae42 100644
--- a/src/components/Badge.tsx
+++ b/src/components/Badge.tsx
@@ -3,29 +3,25 @@
import { HTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/helper';
+import type { Color, Variant, Size } from '@/types/theme';
export interface BadgeProps
extends Omit, 'className'> {
children?: ReactNode;
className?: {
badge?: string;
+ status?: string;
};
- variant?: 'default' | 'outline' | 'ghost' | 'soft' | 'dash';
- color?:
- | 'neutral'
- | 'primary'
- | 'secondary'
- | 'accent'
- | 'info'
- | 'success'
- | 'warning'
- | 'error';
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ statusIndicator?: boolean;
+ variant?: Variant;
+ color?: Color;
+ size?: Size;
}
const Badge = ({
children,
className,
+ statusIndicator = false,
variant = 'default',
color,
size = 'md',
@@ -34,7 +30,7 @@ const Badge = ({
const getBadgeClasses = () => {
const baseClasses = 'badge';
- const variantClasses = {
+ const variantClasses: Record = {
default: '',
outline: 'badge-outline',
ghost: 'badge-ghost',
@@ -42,7 +38,7 @@ const Badge = ({
dash: 'badge-dash',
};
- const colorClasses = {
+ const colorClasses: Record = {
neutral: 'badge-neutral',
primary: 'badge-primary',
secondary: 'badge-secondary',
@@ -51,9 +47,10 @@ const Badge = ({
success: 'badge-success',
warning: 'badge-warning',
error: 'badge-error',
+ none: '',
};
- const sizeClasses = {
+ const sizeClasses: Record = {
xs: 'badge-xs',
sm: 'badge-sm',
md: 'badge-md',
@@ -70,8 +67,31 @@ const Badge = ({
);
};
+ const getStatusClasses = () => {
+ if (!statusIndicator) return '';
+
+ const statusIndicatorClasses: Record = {
+ neutral: 'bg-neutral',
+ primary: 'bg-primary',
+ secondary: 'bg-secondary',
+ accent: 'bg-accent',
+ info: 'bg-info',
+ success: 'bg-success',
+ warning: 'bg-warning',
+ error: 'bg-error',
+ none: '',
+ };
+
+ return cn(
+ 'w-2.5 h-2.5 rounded-full',
+ color && statusIndicatorClasses[color],
+ className?.status
+ );
+ };
+
return (
+ {statusIndicator && }
{children}
);
diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx
index 4cb59cdb..7b5e2374 100644
--- a/src/components/Drawer.tsx
+++ b/src/components/Drawer.tsx
@@ -15,6 +15,8 @@ interface DrawerProps {
className?: DrawerClassName;
onBackdropClick?: () => void;
closeOnBackdropClick?: boolean;
+ expandedContent?: ReactNode;
+ expandedWidth?: string;
}
type DrawerClassName = {
@@ -36,6 +38,8 @@ const Drawer = ({
className,
onBackdropClick,
closeOnBackdropClick = true,
+ expandedContent,
+ expandedWidth = 'w-[400px]',
}: DrawerProps) => {
const getDrawerClassNames = (): DrawerClassName => {
const baseClassNames = {
@@ -46,12 +50,21 @@ const Drawer = ({
drawerSidebarContent: 'min-h-full bg-base-100',
};
+ const getSidebarWidth = () => {
+ if (variant === 'sidebar') {
+ return expandedContent
+ ? 'w-full lg:min-w-[600px] lg:max-w-[600px]'
+ : 'w-full max-w-[300px] lg:w-[300px]';
+ }
+ return 'w-full sm:min-w-120 sm:w-fit';
+ };
+
if (variant === 'sidebar') {
return {
...baseClassNames,
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full max-w-[300px] lg:w-[300px]'
+ getSidebarWidth()
),
};
} else if (variant === 'right') {
@@ -60,11 +73,11 @@ const Drawer = ({
drawer: cn(baseClassNames.drawer, 'drawer-end'),
drawerSide: cn(
baseClassNames.drawerSide,
- 'border-l border-solid border-gray-200 drawer-side w-screen top-0 right-0 fixed z-21'
+ 'border-l border-solid border-gray-200 sm:drawer-side w-screen top-0 right-0 fixed z-21'
),
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full sm:min-w-120 sm:w-fit'
+ getSidebarWidth()
),
};
} else if (variant === 'left') {
@@ -76,7 +89,7 @@ const Drawer = ({
),
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full sm:min-w-120 sm:w-fit'
+ getSidebarWidth()
),
};
}
@@ -138,14 +151,37 @@ const Drawer = ({
onClick={closeDrawer}
/>
- {/* Sidebar Content */}
+ {/* Sidebar Content - Full height container */}
- {sidebarContent}
+ {/* Primary Sidebar Content */}
+
+ {sidebarContent}
+
+
+ {/* Expanded Drawer (Right side, side-by-side) */}
+ {expandedContent && (
+
+
+ {expandedContent}
+
+
+ )}
diff --git a/src/components/modal/ConfirmationModal.tsx b/src/components/modal/ConfirmationModal.tsx
index 00b63c86..9cf17008 100644
--- a/src/components/modal/ConfirmationModal.tsx
+++ b/src/components/modal/ConfirmationModal.tsx
@@ -8,10 +8,13 @@ import Button, { ButtonProps } from '@/components/Button';
import { cn } from '@/lib/helper';
+export type IconPosition = 'left' | 'center' | 'right';
+
export interface ConfirmationModalProps {
ref: RefObject;
type?: 'info' | 'success' | 'error';
text?: string;
+ subtitleText?: string;
closeOnBackdrop?: boolean;
primaryButton?: ButtonProps & {
text?: string;
@@ -24,17 +27,84 @@ export interface ConfirmationModalProps {
modalBox?: string;
};
children?: React.ReactNode;
+ iconSize?: number;
+ iconPosition?: IconPosition;
}
+const iconConfig = {
+ info: {
+ icon: 'material-symbols:info-outline-rounded',
+ iconClassName: 'text-info-content',
+ bgClassName: 'bg-info',
+ outerRingClassName: 'bg-info/20',
+ borderClassName: 'border-info',
+ },
+ success: {
+ icon: 'heroicons:check',
+ iconClassName: 'text-white',
+ bgClassName: 'bg-[#00D390]',
+ outerRingClassName: 'bg-[#00D3901F]',
+ borderClassName: 'border-[#CCF7EB]',
+ },
+ error: {
+ icon: 'solar:danger-triangle-linear',
+ iconClassName: 'text-error-content',
+ bgClassName: 'bg-[#f03338]',
+ outerRingClassName: 'bg-[#f3cdcd]',
+ borderClassName: 'border-[#fff0ef]',
+ },
+} as const;
+
+const ConfirmationModalIcon = ({
+ type,
+ size = 24,
+}: {
+ type: 'info' | 'success' | 'error';
+ size?: number;
+}) => {
+ const config = iconConfig[type];
+
+ return (
+
+ );
+};
+
const ConfirmationModal = ({
ref,
type = 'info',
text,
+ subtitleText,
closeOnBackdrop,
primaryButton,
secondaryButton,
className,
children,
+ iconSize = 32,
+ iconPosition = 'center',
}: ConfirmationModalProps) => {
const [isPrimaryButtonLoading, setIsPrimaryButtonLoading] = useState(false);
@@ -55,47 +125,44 @@ const ConfirmationModal = ({
return (
-
- {type === 'info' && (
-
- )}
+ {iconPosition === 'center' ? (
+ <>
+
+
+
- {type === 'success' && (
-
- )}
+
+ {text ?? 'Apakah anda yakin ingin melakukan hal ini?'}
+
- {type === 'error' && (
-
- )}
-
+ {subtitleText && (
+
+ {subtitleText}
+
+ )}
+ >
+ ) : (
+
+
+
+
-
- {text ?? 'Apakah anda yakin ingin melakukan hal ini?'}
-
+
+
+ {text ?? 'Apakah anda yakin ingin melakukan hal ini?'}
+
+
+ {subtitleText && (
+
{subtitleText}
+ )}
+
+
+ )}
{children &&
{children}
}
@@ -103,7 +170,7 @@ const ConfirmationModal = ({
{secondaryButton && secondaryButton.text && (