feat(FE): create floation actions button

This commit is contained in:
randy-ar
2025-12-02 12:37:03 +07:00
parent 6b5838b5aa
commit 9eba5ffeca
3 changed files with 184 additions and 3 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ export default function ProjectFlockLayout({
return (
<>
{/* List page always rendered */}
<div>
<div className='min-h-sceen w-full relative'>
<ProjectFlockTable
refresh={() => !isOpen && router.push('/production/project-flock')}
/>
@@ -48,7 +48,7 @@ export default function ProjectFlockLayout({
setOpen={(v) => {
if (!v) router.push('/production/project-flock');
}}
closeOnBackdropClick={false}
closeOnBackdropClick={isDetail ? true : false}
onBackdropClick={handleBackdropClick}
variant='right'
sidebarContent={isOpen && <div className='p-4'>{children}</div>}
+127
View File
@@ -0,0 +1,127 @@
'use client';
import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react';
type FloatingActionsButtonProps = {
actions: {
action: 'DETAIL' | 'EDIT' | 'DELETE';
icon: string;
label?: string;
onClick?: () => void;
hidden?: boolean;
}[];
approvals: {
action: 'APPROVED' | 'REJECTED';
icon: string;
label?: string;
onClick?: () => void;
}[];
selectedRowIds: number[];
onClose: () => void;
};
const FloatingActionsButton = ({
actions,
approvals,
selectedRowIds,
onClose,
}: FloatingActionsButtonProps) => {
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB
const positionStyles =
selectedRowIds.length > 0 ? 'bottom-[10%]' : 'bottom-[-100%]';
// Helper untuk menentukan gaya warna tombol approval
const getApprovalColor = (action: 'APPROVED' | 'REJECTED') => {
if (action === 'APPROVED') return 'success';
if (action === 'REJECTED') return 'error';
return 'primary';
};
const getActionColor = (action: 'DETAIL' | 'EDIT' | 'DELETE') => {
if (action === 'DETAIL') return 'white';
if (action === 'EDIT') return 'warning';
if (action === 'DELETE') return 'error';
return 'primary';
};
return (
// Container utama FAB
<div
className={cn(
`absolute ${positionStyles} inset-x-1/2 -translate-x-1/2 z-50`,
'mx-auto w-full max-w-xl sm:mx-0 bg-base-300 p-4 rounded-xl shadow-md transition-all duration-300 transform',
'bg-slate-950 backdrop-blur-md'
)}
>
<div className='flex flex-col gap-3'>
{/* === BARIS ATAS: Status Seleksi dan Actions (Termasuk Close) === */}
<div className='flex justify-between items-center text-white'>
<h4 className='text-base font-semibold'>
{selectedRowIds.length} Selected
</h4>
<div className='flex flex-row gap-1 items-stretch'>
<div className='flex gap-4 items-center'>
{/* Render Aksi dari props.actions */}
{actions
.filter((action) => !action.hidden)
.map((action, index) => {
return (
<button
key={index}
onClick={action.onClick}
className='text-white hover:text-gray-400 tooltip tooltip-bottom'
data-tip={action.label}
>
<Icon
icon={action.icon}
width={24}
height={24}
className={`text-${getActionColor(action.action)} font-thin`}
/>
</button>
);
})}
<div className='border-[0.5px] border-white/30 h-full'></div>
{/* Tombol Close */}
<button
onClick={onClose}
className='text-white hover:text-gray-400'
>
<Icon icon='mdi:close' width={24} height={24} />
</button>
</div>
</div>
</div>
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
<div className={`grid grid-cols-${approvals.length} gap-3`}>
{approvals.map((approval, index) => (
<button
key={index}
onClick={approval.onClick}
className={cn(
'btn btn-lg w-full',
'bg-white/20 hover:bg-white/40 border-white/30 hover:border-white/50',
'text-white/50 hover:text-white/100 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200'
)}
>
<Icon
icon={approval.icon}
width={20}
height={20}
className={`text-${getApprovalColor(approval.action)}`}
/>
{approval.label || approval.action}
</button>
))}
</div>
</div>
</div>
);
};
export default FloatingActionsButton;
@@ -1,6 +1,7 @@
'use client';
import Button from '@/components/Button';
import FloatingActionsButton from '@/components/FloatingActionsButton';
import CheckboxInput from '@/components/input/CheckboxInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
@@ -20,6 +21,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react';
import { CellContext, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation';
import { ChangeEventHandler, useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
@@ -119,6 +121,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
periodFilter: 'period',
},
});
const router = useRouter();
// State
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
@@ -262,7 +265,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
return (
<>
<div className='w-full p-0 sm:p-4'>
<div className='min-h-screen w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col justify-between items-end gap-2'>
<div className='flex flex-col sm:flex-row gap-3 w-full'>
@@ -577,6 +580,57 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
</div>
</div>
<FloatingActionsButton
actions={[
{
action: 'DETAIL',
icon: 'mdi:eye-outline',
label: 'Lihat Detail',
hidden: selectedRowIds.length !== 1,
onClick() {
router.push(
`/production/project-flock/detail?projectFlockId=${selectedRowIds[0]}`
);
setRowSelection({});
},
},
{
action: 'DELETE',
icon: 'material-symbols:delete-outline-rounded',
label: 'Hapus Massal',
onClick: () => {
toast.error(`Konfirmasi hapus ${selectedRowIds.length} data.`);
},
},
]}
approvals={[
{
icon: 'material-symbols:check',
label: 'Approve',
action: 'APPROVED',
onClick: () => {
setApprovalAction('APPROVED');
confirmModal.openModal();
setRowSelection({});
},
},
{
icon: 'mdi:times',
label: 'Reject',
action: 'REJECTED',
onClick: () => {
setApprovalAction('REJECTED');
confirmModal.openModal();
setRowSelection({});
},
},
]}
selectedRowIds={selectedRowIds}
onClose={() => {
setRowSelection({});
}}
/>
<ConfirmationModal
ref={deleteModal.ref}
type='error'