diff --git a/src/components/pages/uniformity/UniformityTable.tsx b/src/components/pages/uniformity/UniformityTable.tsx index dbfcf9f2..05e1fdd9 100644 --- a/src/components/pages/uniformity/UniformityTable.tsx +++ b/src/components/pages/uniformity/UniformityTable.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; -import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; +import { ColumnDef, SortingState } from '@tanstack/react-table'; import { cn, formatDate } from '@/lib/helper'; import Button from '@/components/Button'; import UniformityChart from '@/components/pages/uniformity/UniformityChart'; @@ -15,9 +15,6 @@ import { isResponseSuccess } from '@/lib/api-helper'; import Table from '@/components/Table'; import Badge from '@/components/Badge'; import CheckboxInput from '@/components/input/CheckboxInput'; -import RowDropdownOptions from '@/components/table/RowDropdownOptions'; -import RowCollapseOptions from '@/components/table/RowCollapseOptions'; -import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import toast from 'react-hot-toast'; @@ -25,6 +22,7 @@ import Card from '@/components/Card'; import UniformityTableSkeleton from './skeleton/UniformityTableSkeleton'; import RequirePermission from '@/components/helper/RequirePermission'; import { useUniformityStore } from '@/stores/uniformity/uniformity.store'; +import FloatingActionsButton from '@/components/FloatingActionsButton'; const statusColorMap: Record = { APPROVED: 'bg-[#00D39033]', @@ -60,68 +58,6 @@ const isUniformityLocked = (uniformity: Uniformity): boolean => { return uniformity.status === 'APPROVED' || uniformity.status === 'REJECTED'; }; -const RowOptionsMenu = ({ - type = 'dropdown', - props, - deleteClickHandler, - setSelectedUniformity, - openModal, -}: { - type: 'dropdown' | 'collapse'; - props: CellContext; - deleteClickHandler: () => void; - setSelectedUniformity: (uniformity: Uniformity) => void; - openModal: () => void; -}) => { - const handleDeleteClick = useCallback(() => { - setSelectedUniformity(props.row.original); - openModal(); - }, [props.row.original, setSelectedUniformity, openModal]); - - return ( - - - - - - - - - - - - ); -}; - const UniformityTable = ({ refresh }: { refresh?: () => void }) => { const isSuccess = useUniformityStore((s) => s.isSuccess); const setIsSuccess = useUniformityStore((s) => s.setIsSuccess); @@ -143,14 +79,41 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); - const [selectedUniformity, setSelectedUniformity] = useState< - Uniformity | undefined - >(undefined); + const [selectedUniformity] = useState(undefined); const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [isBulkActionLoading, setIsBulkActionLoading] = useState(false); const singleDeleteModal = useModal(); + const bulkDeleteModal = useModal(); const successModal = useModal(); + const { + data: uniformities, + isLoading, + mutate: refreshUniformities, + } = useSWR( + `${UniformityApi.basePath}${getTableFilterQueryString()}`, + UniformityApi.getAllFetcher + ); + + const selectedRowIds = useMemo(() => { + return Object.keys(rowSelection) + .filter((key) => rowSelection[key]) + .map((key) => parseInt(key)); + }, [rowSelection]); + + const selectedUniformities = useMemo(() => { + if (!isResponseSuccess(uniformities) || !uniformities.data) return []; + return uniformities.data.filter((u) => selectedRowIds.includes(u.id)); + }, [uniformities, selectedRowIds]); + + const canApproveReject = useMemo(() => { + return ( + selectedUniformities.length > 0 && + selectedUniformities.every((u) => u.status === 'CREATED') + ); + }, [selectedUniformities]); + useEffect(() => { if (isSuccess) { successModal.openModal(); @@ -162,15 +125,6 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { setIsSuccess(false); }; - const { - data: uniformities, - isLoading, - mutate: refreshUniformities, - } = useSWR( - `${UniformityApi.basePath}${getTableFilterQueryString()}`, - UniformityApi.getAllFetcher - ); - const singleDeleteHandler = useCallback(async () => { setIsDeleteLoading(true); @@ -182,6 +136,72 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { setIsDeleteLoading(false); }, [selectedUniformity?.id, refreshUniformities, singleDeleteModal]); + const handleBulkDelete = useCallback(() => { + bulkDeleteModal.openModal(); + }, [bulkDeleteModal]); + + const bulkDeleteHandler = useCallback(async () => { + setIsBulkActionLoading(true); + + try { + await UniformityApi.bulkDelete(selectedRowIds); + + setRowSelection({}); + refreshUniformities(); + + bulkDeleteModal.closeModal(); + toast.success( + `Successfully deleted ${selectedRowIds.length} Uniformity data!` + ); + } catch { + toast.error('Failed to delete Uniformity data'); + } finally { + setIsBulkActionLoading(false); + } + }, [selectedRowIds, refreshUniformities, bulkDeleteModal]); + + const handleCloseFab = useCallback(() => { + setRowSelection({}); + }, []); + + const handleBulkApprove = useCallback(async () => { + setIsBulkActionLoading(true); + + try { + await UniformityApi.approve(selectedRowIds); + + setRowSelection({}); + refreshUniformities(); + + toast.success( + `Successfully approved ${selectedRowIds.length} Uniformity data!` + ); + } catch { + toast.error('Failed to approve Uniformity data'); + } finally { + setIsBulkActionLoading(false); + } + }, [selectedRowIds, refreshUniformities]); + + const handleBulkReject = useCallback(async () => { + setIsBulkActionLoading(true); + + try { + await UniformityApi.reject(selectedRowIds); + + setRowSelection({}); + refreshUniformities(); + + toast.success( + `Successfully rejected ${selectedRowIds.length} Uniformity data!` + ); + } catch (error) { + toast.error('Failed to reject Uniformity data'); + } finally { + setIsBulkActionLoading(false); + } + }, [selectedRowIds, refreshUniformities]); + useEffect(() => { if (isResponseSuccess(uniformities) && uniformities.data) { const newSelection: Record = {}; @@ -316,53 +336,6 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { return {uniformity}%; }, }, - { - id: 'actions', - header: 'Aksi', - cell: (props: CellContext) => { - 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; - - return ( - <> - {currentPageSize > 2 && ( - - { - setSelectedUniformity(props.row.original); - singleDeleteModal.openModal(); - }} - setSelectedUniformity={setSelectedUniformity} - openModal={singleDeleteModal.openModal} - /> - - )} - - {currentPageSize <= 2 && ( - - { - setSelectedUniformity(props.row.original); - singleDeleteModal.openModal(); - }} - setSelectedUniformity={setSelectedUniformity} - openModal={singleDeleteModal.openModal} - /> - - )} - - ); - }, - }, ], [] ); @@ -407,7 +380,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { @@ -459,6 +432,21 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => { }} /> + + void }) => { /> + + {/* Floating Actions Button */} + ); diff --git a/src/services/api/uniformity.ts b/src/services/api/uniformity.ts index 7ef16e91..4728e3e7 100644 --- a/src/services/api/uniformity.ts +++ b/src/services/api/uniformity.ts @@ -61,6 +61,56 @@ export class UniformityApiService extends BaseApiService< } ); } + + async approve( + idOrIds: number | number[], + notes?: string + ): Promise | undefined> { + const approvable_ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; + return await this.customRequest>( + 'approvals', + { + method: 'POST', + payload: { + action: 'APPROVED', + approvable_ids, + notes, + }, + } + ); + } + + async reject( + idOrIds: number | number[], + notes: string = '' + ): Promise | undefined> { + const approvable_ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; + return await this.customRequest>( + 'approvals', + { + method: 'POST', + payload: { + action: 'REJECTED', + approvable_ids, + notes, + }, + } + ); + } + + async bulkDelete( + ids: number[] + ): Promise | undefined> { + return await this.customRequest>( + 'bulk-delete', + { + method: 'POST', + payload: { + ids, + }, + } + ); + } } export const UniformityApi = new UniformityApiService(