mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 14:55:44 +00:00
feat(FE): create floation actions button
This commit is contained in:
@@ -36,7 +36,7 @@ export default function ProjectFlockLayout({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* List page always rendered */}
|
{/* List page always rendered */}
|
||||||
<div>
|
<div className='min-h-sceen w-full relative'>
|
||||||
<ProjectFlockTable
|
<ProjectFlockTable
|
||||||
refresh={() => !isOpen && router.push('/production/project-flock')}
|
refresh={() => !isOpen && router.push('/production/project-flock')}
|
||||||
/>
|
/>
|
||||||
@@ -48,7 +48,7 @@ export default function ProjectFlockLayout({
|
|||||||
setOpen={(v) => {
|
setOpen={(v) => {
|
||||||
if (!v) router.push('/production/project-flock');
|
if (!v) router.push('/production/project-flock');
|
||||||
}}
|
}}
|
||||||
closeOnBackdropClick={false}
|
closeOnBackdropClick={isDetail ? true : false}
|
||||||
onBackdropClick={handleBackdropClick}
|
onBackdropClick={handleBackdropClick}
|
||||||
variant='right'
|
variant='right'
|
||||||
sidebarContent={isOpen && <div className='p-4'>{children}</div>}
|
sidebarContent={isOpen && <div className='p-4'>{children}</div>}
|
||||||
|
|||||||
@@ -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';
|
'use client';
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import FloatingActionsButton from '@/components/FloatingActionsButton';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
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 { ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { CellContext, SortingState } from '@tanstack/react-table';
|
import { CellContext, SortingState } from '@tanstack/react-table';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { ChangeEventHandler, useEffect, useState } from 'react';
|
import { ChangeEventHandler, useEffect, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@@ -119,6 +121,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
periodFilter: 'period',
|
periodFilter: 'period',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
@@ -262,7 +265,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
|
|
||||||
return (
|
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='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col justify-between items-end gap-2'>
|
<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'>
|
<div className='flex flex-col sm:flex-row gap-3 w-full'>
|
||||||
@@ -577,6 +580,57 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
</div>
|
</div>
|
||||||
</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
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
|
|||||||
Reference in New Issue
Block a user