mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
fix: adjust ProjectFlockTable UI
This commit is contained in:
@@ -13,6 +13,7 @@ import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||
import Table from '@/components/Table';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { cn, formatDate, formatTitleCase } from '@/lib/helper';
|
||||
@@ -29,6 +30,111 @@ import toast from 'react-hot-toast';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
import PopoverContent from '@/components/popover/PopoverContent';
|
||||
|
||||
const RowOptionsMenu = ({
|
||||
props,
|
||||
popoverPosition = 'bottom',
|
||||
editClickHandler,
|
||||
detailClickHandler,
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
props: CellContext<ProjectFlock, unknown>;
|
||||
popoverPosition: 'bottom' | 'top';
|
||||
editClickHandler: (id: number) => void;
|
||||
detailClickHandler: (id: number) => void;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
// TODO: change this to real condition
|
||||
const showEditButton = true;
|
||||
|
||||
const showDeleteButton = showEditButton;
|
||||
|
||||
const popoverId = `projectFlock#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-projectFlock#${props.row.original.id}`;
|
||||
|
||||
const closePopover = () => {
|
||||
document.getElementById(popoverId)?.hidePopover();
|
||||
};
|
||||
|
||||
const detailClickHandlerWrapper = () => {
|
||||
detailClickHandler(props.row.original.id);
|
||||
closePopover();
|
||||
};
|
||||
|
||||
const editClickHandlerWrapper = () => {
|
||||
editClickHandler(props.row.original.id);
|
||||
closePopover();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<PopoverButton
|
||||
tabIndex={0}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
popoverTarget={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
>
|
||||
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverContent
|
||||
id={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
|
||||
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
|
||||
>
|
||||
<div className='flex flex-col bg-base-100 rounded-xl'>
|
||||
<RequirePermission permissions='lti.production.project_flocks.detail'>
|
||||
<Button
|
||||
// href={`/production/project-flock/detail/?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={detailClickHandlerWrapper}
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:eye' width={20} height={20} />
|
||||
View Details
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
{showEditButton && (
|
||||
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||
<Button
|
||||
// href={`/production/project-flock/detail/edit/?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={editClickHandlerWrapper}
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:pencil-square' width={20} height={20} />
|
||||
Edit
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
|
||||
{showDeleteButton && (
|
||||
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||
<hr className='mx-3 border-base-content/10 h-px' />
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
const {
|
||||
@@ -62,6 +168,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
const selectedRowIds = Object.keys(rowSelection)
|
||||
.filter((id) => rowSelection[id])
|
||||
.map((id) => parseInt(id));
|
||||
|
||||
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||
null
|
||||
@@ -78,6 +185,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||
useState(false);
|
||||
|
||||
// ===== Fetch Data =====
|
||||
const {
|
||||
@@ -175,14 +284,27 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
: null;
|
||||
}, [rowSelection]);
|
||||
|
||||
// const canApprove = useMemo(() => {
|
||||
// if (!selectedSingleRow || isApproveLoading) return false;
|
||||
|
||||
// const isPengajuan = selectedSingleRow.approval?.step_number == 1;
|
||||
// const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED';
|
||||
|
||||
// return isPengajuan && isNotRejected;
|
||||
// }, [selectedSingleRow, isApproveLoading]);
|
||||
|
||||
const canApprove = useMemo(() => {
|
||||
if (!selectedSingleRow || isApproveLoading) return false;
|
||||
return selectedRowIds.every((id) => {
|
||||
const projectFlock = isResponseSuccess(projectFlocks)
|
||||
? projectFlocks?.data.find((row) => row.id === id)
|
||||
: null;
|
||||
|
||||
const isPengajuan = selectedSingleRow.approval?.step_number == 1;
|
||||
const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED';
|
||||
|
||||
return isPengajuan && isNotRejected;
|
||||
}, [selectedSingleRow, isApproveLoading]);
|
||||
const isProjectFlockRequesting = projectFlock?.approval?.step_number == 1;
|
||||
const isProjectFlockNotRejected =
|
||||
projectFlock?.approval?.action != 'REJECTED';
|
||||
return isProjectFlockRequesting && isProjectFlockNotRejected;
|
||||
});
|
||||
}, [selectedRowIds, projectFlocks]);
|
||||
|
||||
// ====== COLUMNS ======
|
||||
const columns = useMemo<ColumnDef<ProjectFlock>[]>(
|
||||
@@ -256,44 +378,39 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
const approval = props.row.original.approval;
|
||||
const isRejected = approval?.action == 'REJECTED';
|
||||
const isApproved = approval?.action == 'APPROVED';
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2 w-full flex flex-row justify-start',
|
||||
}}
|
||||
color={
|
||||
isRejected
|
||||
? 'error'
|
||||
: isApproved
|
||||
? approval?.step_number == 1
|
||||
? 'neutral'
|
||||
: approval?.step_number == 2
|
||||
? 'primary'
|
||||
: approval?.step_number == 3
|
||||
? 'success'
|
||||
: 'neutral'
|
||||
|
||||
let latestApprovalStepName = approval.step_name;
|
||||
|
||||
const badgeColor = isRejected
|
||||
? 'error'
|
||||
: isApproved
|
||||
? approval?.step_number == 1
|
||||
? 'neutral'
|
||||
: approval?.step_number == 2
|
||||
? 'success'
|
||||
: approval?.step_number == 3
|
||||
? 'error'
|
||||
: 'neutral'
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon='mdi:circle'
|
||||
width={12}
|
||||
height={12}
|
||||
color={
|
||||
approval?.step_number == 1
|
||||
? 'neutral'
|
||||
: approval?.step_number == 2
|
||||
? 'primary'
|
||||
: approval?.step_number == 3
|
||||
? 'success'
|
||||
: 'neutral'
|
||||
}
|
||||
/>
|
||||
{isRejected
|
||||
? 'Ditolak'
|
||||
: formatTitleCase(approval?.step_name || '')}
|
||||
</Badge>
|
||||
: 'neutral';
|
||||
|
||||
switch (approval.action.toLowerCase()) {
|
||||
case 'pengajuan':
|
||||
latestApprovalStepName = 'Pengajuan';
|
||||
break;
|
||||
case 'aktif':
|
||||
latestApprovalStepName = 'Aktif';
|
||||
break;
|
||||
case 'Selesai':
|
||||
latestApprovalStepName = 'Closing';
|
||||
break;
|
||||
}
|
||||
|
||||
if (isRejected) {
|
||||
latestApprovalStepName = 'Ditolak';
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusBadge color={badgeColor} text={latestApprovalStepName} />
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -325,27 +442,88 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
cell: (props) =>
|
||||
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: (props) => {
|
||||
const currentPageSize =
|
||||
props.table.getPaginationRowModel().rows.length;
|
||||
const currentPageRows = props.table.getPaginationRowModel().flatRows;
|
||||
const currentRowRelativeIndex =
|
||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||
|
||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||
|
||||
const detailClickHandler = (id: number) => {
|
||||
router.push(
|
||||
`/production/project-flock/detail/?projectFlockId=${id}`
|
||||
);
|
||||
};
|
||||
|
||||
const editClickHandler = (id: number) => {
|
||||
router.push(
|
||||
`/production/project-flock/detail/edit/?projectFlockId=${id}`
|
||||
);
|
||||
};
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
// Set row selection
|
||||
setRowSelection({
|
||||
[String(props.row.original.id)]: true,
|
||||
});
|
||||
|
||||
deleteModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
detailClickHandler={detailClickHandler}
|
||||
editClickHandler={editClickHandler}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const exportToExcelHandler = async () => {
|
||||
setIsLoadingExportingToExcel(true);
|
||||
|
||||
toast.error('Not implemented yet!');
|
||||
|
||||
setIsLoadingExportingToExcel(false);
|
||||
};
|
||||
|
||||
const bulkApproveClickHandler = () => {
|
||||
setApprovalAction('APPROVED');
|
||||
confirmModal.openModal();
|
||||
};
|
||||
|
||||
const bulkRejectClickHandler = () => {
|
||||
setApprovalAction('REJECTED');
|
||||
confirmModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='min-h-screen w-full 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='@container min-h-screen w-full'>
|
||||
<div className='flex flex-col 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'>
|
||||
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||
<Button
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
onClick={() => {
|
||||
setRowSelection({});
|
||||
router.push('/production/project-flock/add');
|
||||
}}
|
||||
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah
|
||||
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||
Add Flock
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<div className='ms-auto w-full sm:w-auto'>
|
||||
@@ -423,6 +601,158 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
|
||||
<div className='w-fit flex flex-row gap-3 flex-wrap'>
|
||||
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
setRowSelection({});
|
||||
router.push('/production/project-flock/add');
|
||||
}}
|
||||
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||
>
|
||||
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||
Add Flock
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
{selectedRowIds.length > 0 && canApprove && (
|
||||
<>
|
||||
<hr className='w-px h-full border-none bg-base-content/10 hidden @sm:block' />
|
||||
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
onClick={bulkRejectClickHandler}
|
||||
disabled={selectedRowIds.length === 0}
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:x-mark'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-error'
|
||||
/>
|
||||
Reject
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={selectedRowIds.length === 0}
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:check'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-success'
|
||||
/>
|
||||
Approve
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
placeholder='Search'
|
||||
value={tableFilterState.search ?? ''}
|
||||
onChange={searchChangeHandler}
|
||||
startAdornment={
|
||||
<Icon
|
||||
icon='heroicons:magnifying-glass'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24 max-w-3xs',
|
||||
inputWrapper: 'rounded-xl! shadow-button-soft',
|
||||
input:
|
||||
'placeholder:font-semibold placeholder:text-base-content/50',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
// onClick={filterModal.openModal}
|
||||
className={cn(
|
||||
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
|
||||
{
|
||||
// 'border-primary-gradient text-primary': isFilterActive,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
Filter
|
||||
{/* {isFilterActive && (
|
||||
<Badge
|
||||
className={{
|
||||
badge:
|
||||
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
|
||||
}}
|
||||
>
|
||||
{filterCount}
|
||||
</Badge>
|
||||
)} */}
|
||||
</Button>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={exportToExcelHandler}
|
||||
isLoading={isLoadingExportingToExcel}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table<ProjectFlock>
|
||||
@@ -448,26 +778,20 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
setSorting={setSorting}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
withCheckbox
|
||||
className={{
|
||||
containerClassName: cn({
|
||||
'mb-40':
|
||||
containerClassName: cn('p-3', {
|
||||
'w-full mb-20':
|
||||
isResponseSuccess(projectFlocks) &&
|
||||
projectFlocks?.data?.length > 0,
|
||||
projectFlocks?.data?.length === 0,
|
||||
}),
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||
headerColumnClassName: 'text-nowrap',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FloatingActionsButton
|
||||
{/* <FloatingActionsButton
|
||||
actions={[
|
||||
{
|
||||
action: 'DETAIL',
|
||||
@@ -520,7 +844,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
onClose={() => {
|
||||
setRowSelection({});
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
|
||||
Reference in New Issue
Block a user