mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE): Add delete functionality to Inventory and Movement tables
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
|
||||
import { useFormik } from 'formik';
|
||||
@@ -26,6 +26,10 @@ import { InventoryAdjustmentApi } from '@/services/api/inventory';
|
||||
import { WarehouseApi, ProductApi } from '@/services/api/master-data';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
import PopoverContent from '@/components/popover/PopoverContent';
|
||||
import toast from 'react-hot-toast';
|
||||
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
import { TRANSACTION_SUBTYPE_OPTIONS } from '@/config/constant';
|
||||
@@ -38,6 +42,62 @@ import {
|
||||
AdjustmentFilterType,
|
||||
} from '@/components/pages/inventory/adjustment/filter/AdjustmentFilter';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
import { CellContext } from '@tanstack/react-table';
|
||||
|
||||
const RowOptionsMenu = ({
|
||||
popoverPosition = 'bottom',
|
||||
props,
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
popoverPosition: 'bottom' | 'top';
|
||||
props: CellContext<InventoryAdjustment, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const popoverId = `adjustment#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-adjustment#${props.row.original.id}`;
|
||||
|
||||
const closePopover = () => {
|
||||
document.getElementById(popoverId)?.hidePopover();
|
||||
};
|
||||
|
||||
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.inventory.delete'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteClickHandler();
|
||||
closePopover();
|
||||
}}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InventoryAdjustmentTable = () => {
|
||||
const { searchValue, setSearchValue, setTableState } = useUiStore();
|
||||
@@ -182,12 +242,31 @@ const InventoryAdjustmentTable = () => {
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
const { data: inventoryAdjustments, isLoading } = useSWR(
|
||||
const { data: inventoryAdjustments, isLoading, mutate: refreshAdjustments } = useSWR(
|
||||
`${InventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
|
||||
InventoryAdjustmentApi.getAllFetcher
|
||||
);
|
||||
|
||||
const singleDeleteHandler = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
const response = await InventoryAdjustmentApi.delete(selectedAdjustment?.id as number);
|
||||
|
||||
singleDeleteModal.closeModal();
|
||||
setIsDeleteLoading(false);
|
||||
|
||||
if (isResponseSuccess(response)) {
|
||||
toast.success(response?.message || 'Successfully delete Adjustment!');
|
||||
refreshAdjustments();
|
||||
} else {
|
||||
toast.error(response?.message || 'Failed to delete Adjustment');
|
||||
}
|
||||
};
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [selectedAdjustment, setSelectedAdjustment] = useState<InventoryAdjustment | undefined>(undefined);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const singleDeleteModal = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
updateFilter('search', searchValue);
|
||||
@@ -302,8 +381,32 @@ const InventoryAdjustmentTable = () => {
|
||||
header: 'Oleh',
|
||||
accessorFn: (row) => row.created_user?.name ?? '-',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Aksi',
|
||||
cell: (props: CellContext<InventoryAdjustment, unknown>) => {
|
||||
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 deleteClickHandler = () => {
|
||||
setSelectedAdjustment(props.row.original);
|
||||
singleDeleteModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[tableFilterState.pageSize, tableFilterState.page]
|
||||
[tableFilterState.pageSize, tableFilterState.page, singleDeleteModal, setSelectedAdjustment]
|
||||
);
|
||||
|
||||
const updateSortingFilter = useCallback(
|
||||
@@ -532,6 +635,21 @@ const InventoryAdjustmentTable = () => {
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Adjustment ini?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table';
|
||||
import { useFormik } from 'formik';
|
||||
|
||||
@@ -21,6 +21,8 @@ import { cn } from '@/lib/helper';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import toast from 'react-hot-toast';
|
||||
import Button from '@/components/Button';
|
||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import SelectInput, { useSelect } from '@/components/input/SelectInput';
|
||||
@@ -41,9 +43,11 @@ import {
|
||||
const RowOptionsMenu = ({
|
||||
popoverPosition = 'bottom',
|
||||
props,
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
popoverPosition: 'bottom' | 'top';
|
||||
props: CellContext<Movement, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const popoverId = `movement#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-movement#${props.row.original.id}`;
|
||||
@@ -83,6 +87,20 @@ const RowOptionsMenu = ({
|
||||
Detail
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<RequirePermission permissions='lti.inventory.transfer.delete'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteClickHandler();
|
||||
closePopover();
|
||||
}}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
@@ -206,12 +224,31 @@ const MovementTable = () => {
|
||||
};
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [selectedMovement, setSelectedMovement] = useState<Movement | undefined>(undefined);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const singleDeleteModal = useModal();
|
||||
|
||||
const { data: movements, isLoading } = useSWR(
|
||||
const { data: movements, isLoading, mutate: refreshMovements } = useSWR(
|
||||
`${MovementApi.basePath}${getTableFilterQueryString()}`,
|
||||
MovementApi.getAllFetcher
|
||||
);
|
||||
|
||||
const singleDeleteHandler = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
const response = await MovementApi.delete(selectedMovement?.id as number);
|
||||
|
||||
singleDeleteModal.closeModal();
|
||||
setIsDeleteLoading(false);
|
||||
|
||||
if (isResponseSuccess(response)) {
|
||||
toast.success(response?.message || 'Successfully delete Movement!');
|
||||
refreshMovements();
|
||||
} else {
|
||||
toast.error(response?.message || 'Failed to delete Movement');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateFilter('search', searchValue);
|
||||
}, [searchValue, updateFilter]);
|
||||
@@ -275,16 +312,22 @@ const MovementTable = () => {
|
||||
|
||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
setSelectedMovement(props.row.original);
|
||||
singleDeleteModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[tableFilterState.pageSize, tableFilterState.page]
|
||||
[tableFilterState.pageSize, tableFilterState.page, singleDeleteModal, setSelectedMovement]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -455,6 +498,21 @@ const MovementTable = () => {
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Movement ini?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user