fix(FE): fixing table flickering when input form value

This commit is contained in:
randy-ar
2025-11-19 10:21:59 +07:00
parent a9bdb6c36e
commit f68e59e8c7
4 changed files with 183 additions and 49 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ const TextArea = ({
<textarea
className={cn(
'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all bg-white',
'textarea h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all bg-white',
{
'border-error': isError,
'border-success!': isValid,
+164 -39
View File
@@ -11,14 +11,18 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { TableToolbar } from '@/components/table/TableToolbar';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatCurrency, formatDate } from '@/lib/helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import {
MarketingApi,
SalesOrderApi,
} from '@/services/api/marketing/marketing';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { BaseSalesOrder, Marketing } from '@/types/api/marketing/marketing';
import { Icon } from '@iconify/react';
import { CellContext } from '@tanstack/react-table';
import { CellContext, Row } from '@tanstack/react-table';
import { useCallback, useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
const RowsOptionsMenu = ({
@@ -79,14 +83,11 @@ const SalesOrderTable = () => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [approveAction, setApproveAction] = useState<
'approve' | 'reject' | null
>(null);
const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>(
'APPROVED'
);
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection).filter(
(id) => rowSelection[id]
);
const {
data: marketing,
@@ -115,12 +116,12 @@ const SalesOrderTable = () => {
);
const approveClickHandler = () => {
setApproveAction('approve');
setApproveAction('APPROVED');
confirmationModal.openModal();
};
const rejectClickHandler = () => {
setApproveAction('reject');
setApproveAction('REJECTED');
confirmationModal.openModal();
};
@@ -129,6 +130,82 @@ const SalesOrderTable = () => {
productsModal.openModal();
};
const deleteMarketingHandler = async () => {
const deleteMarketingRes = await MarketingApi.delete(
selectedItem?.id as number
);
if (isResponseSuccess(deleteMarketingRes)) {
confirmationModal.closeModal();
toast.success(deleteMarketingRes?.message as string);
}
if (isResponseError(deleteMarketingRes)) {
confirmationModal.closeModal();
toast.error(deleteMarketingRes?.message as string);
}
refreshMarketing();
deleteModal.closeModal();
};
const allData = isResponseSuccess(marketing) ? marketing.data : [];
const selectedRowsData = allData.filter(
(row) => rowSelection[row.id.toString()]
);
const hasApprovable = selectedRowsData.some(
(row) => row.latest_approval.step_number === 1
);
const hasRejectable = selectedRowsData.some(
(row) => row.latest_approval.step_number === 2
);
const disableApprove = !hasApprovable || hasRejectable;
const disableReject = !hasRejectable || hasApprovable;
const idsToProcess =
approveAction === 'APPROVED'
? selectedRowsData
.filter((row) => row.latest_approval.step_number === 1)
.map((row) => row.id)
: selectedRowsData
.filter((row) => row.latest_approval.step_number === 2)
.map((row) => row.id);
const approveMarketingHandler = async () => {
let idsToProcess: number[] = [];
if (approveAction === 'APPROVED') {
idsToProcess = selectedRowsData
.filter((row) => row.latest_approval.step_number === 1)
.map((row) => row.id);
} else if (approveAction === 'REJECTED') {
idsToProcess = selectedRowsData
.filter((row) => row.latest_approval.step_number === 2)
.map((row) => row.id);
}
if (idsToProcess.length === 0) {
toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`);
confirmationModal.closeModal();
return;
}
const approveMarketingRes = await SalesOrderApi.bulkApprovals(
idsToProcess,
approveAction
);
if (isResponseSuccess(approveMarketingRes)) {
confirmationModal.closeModal();
toast.success(approveMarketingRes?.message as string);
setRowSelection({});
}
if (isResponseError(approveMarketingRes)) {
confirmationModal.closeModal();
toast.error(approveMarketingRes?.message as string);
}
refreshMarketing();
};
const {
state: tableFilterState,
updateFilter,
@@ -143,6 +220,11 @@ const SalesOrderTable = () => {
},
});
const getRowCanSelect = (row: Row<Marketing>): boolean => {
const step = row.original.latest_approval?.step_number;
return step === 1 || step === 2;
};
return (
<>
<div className='flex flex-col gap-4'>
@@ -168,7 +250,7 @@ const SalesOrderTable = () => {
color='success'
onClick={approveClickHandler}
className='justify-start text-sm'
disabled={!selectedRowIds.length}
disabled={disableApprove}
>
<Icon icon='material-symbols:check' width={24} height={24} />
Approve
@@ -178,7 +260,7 @@ const SalesOrderTable = () => {
color='error'
onClick={rejectClickHandler}
className='justify-start text-sm'
disabled={!selectedRowIds.length}
disabled={disableReject}
>
<Icon icon='material-symbols:close' width={24} height={24} />
Reject
@@ -188,31 +270,55 @@ const SalesOrderTable = () => {
<Table
rowSelection={rowSelection}
setRowSelection={setRowSelection}
data={isResponseSuccess(marketing) ? marketing.data : []}
data={allData}
columns={[
{
id: 'select',
header: ({ table }) => (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
</div>
),
cell: ({ row }) => (
<div>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
),
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows.filter(getRowCanSelect);
const allSelected =
selectableRows.length > 0 &&
selectableRows.every((row) => row.getIsSelected());
const someSelected =
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected;
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
selectableRows.forEach((row) =>
row.toggleSelected(shouldSelect)
);
};
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
disabled={selectableRows.length === 0}
/>
</div>
);
},
cell: ({ row }) => {
const canSelect = getRowCanSelect(row);
return (
<div>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!canSelect}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
);
},
},
{
accessorKey: 'so_number',
@@ -285,7 +391,10 @@ const SalesOrderTable = () => {
const isLast2Rows =
currentRowRelativeIndex > currentPageSize - 2;
const deleteClickHandler = () => {};
const deleteClickHandler = () => {
setSelectedItem(props.row.original);
deleteModal.openModal();
};
return (
<>
@@ -343,14 +452,30 @@ const SalesOrderTable = () => {
<ConfirmationModal
ref={confirmationModal.ref}
type={approveAction === 'approve' ? 'success' : 'error'}
text={`Apakah anda yakin ingin ${approveAction} data penjualan (${selectedRowIds.length} data)?`}
type={approveAction === 'APPROVED' ? 'success' : 'error'}
text={`Apakah anda yakin ingin ${approveAction} data penjualan (${idsToProcess.length} data)?`}
secondaryButton={{
text: 'Tidak',
onClick: confirmationModal.closeModal,
}}
primaryButton={{
text: 'Ya',
color: approveAction === 'approve' ? 'success' : 'error',
color: approveAction === 'APPROVED' ? 'success' : 'error',
onClick: approveMarketingHandler,
}}
/>
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text={`Apakah anda yakin ingin menghapus data penjualan ini?`}
secondaryButton={{
text: 'Tidak',
onClick: deleteModal.closeModal,
}}
primaryButton={{
text: 'Ya',
color: 'error',
onClick: deleteMarketingHandler,
}}
/>
@@ -24,7 +24,10 @@ import { CustomerApi } from '@/services/api/master-data';
import { useFormik } from 'formik';
import { SalesOrderFormValues, SalesOrderSchema } from './MarketingForm.schema';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { SalesOrderApi } from '@/services/api/marketing/marketing';
import {
MarketingApi,
SalesOrderApi,
} from '@/services/api/marketing/marketing';
import { SalesOrderProductFormValues } from './repeater/sales-order/SalesOrderProduct.schema';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import toast from 'react-hot-toast';
@@ -180,16 +183,17 @@ const SalesForm = ({
const deleteMarketingHandler = async () => {
setIsLoading(true);
console.log(initialValues?.id);
const deleteMarketingRes = await SalesOrderApi.delete(
const deleteMarketingRes = await MarketingApi.delete(
initialValues?.id as number
);
if (isResponseSuccess(deleteMarketingRes)) {
console.log(deleteMarketingRes);
toast.success(deleteMarketingRes?.message as string);
}
if (isResponseError(deleteMarketingRes)) {
console.log(deleteMarketingRes);
toast.error(deleteMarketingRes?.message as string);
}
toast.success('Successfully deleted Sales Order!');
setIsLoading(false);
deleteModal.closeModal();
router.push('/marketing/sales-orders');
@@ -249,6 +253,8 @@ const SalesForm = ({
[formik]
);
const memoSalesOrder = formik.values.sales_order;
return (
<>
<form
@@ -301,7 +307,7 @@ const SalesForm = ({
<div className='text-green-500'>{JSON.stringify(formik.values)}</div>
<div className='text-red-500'>{JSON.stringify(formik.errors)}</div> */}
<SalesOrderProductTable
data={formik.values.sales_order}
data={memoSalesOrder}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
selectedRowIds={selectedRowIds}
@@ -10,7 +10,7 @@ import {
formatVechicleNumber,
} from '@/lib/helper';
import { Icon } from '@iconify/react';
import { useMemo, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import * as TanStack from '@tanstack/react-table';
import CheckboxInput from '@/components/input/CheckboxInput';
@@ -35,6 +35,9 @@ const SalesOrderProductTable = ({
onBulkDelete,
onAddProductClick,
}: SalesOrderProductTableProps) => {
const onDeleteRef = useRef(onDelete);
onDeleteRef.current = onDelete;
const columns = useMemo(
() => [
{
@@ -114,9 +117,9 @@ const SalesOrderProductTable = ({
<Button
color='error'
className='p-1'
onClick={() => {
onDelete(props.row.original.id as number);
}}
onClick={() =>
onDeleteRef.current(props.row.original.id as number)
}
type='button'
>
<Icon icon='mdi:trash' width={16} height={16} />
@@ -125,7 +128,7 @@ const SalesOrderProductTable = ({
),
},
],
[onDelete]
[]
);
return (