diff --git a/src/app/finance/page.tsx b/src/app/finance/page.tsx
index ec78820c..11a67181 100644
--- a/src/app/finance/page.tsx
+++ b/src/app/finance/page.tsx
@@ -3,12 +3,7 @@
import FinanceTable from '@/components/pages/finance/FinanceTable';
const Finance = () => {
- return (
-
- );
+ return ;
};
export default Finance;
diff --git a/src/app/master-data/area/page.tsx b/src/app/master-data/area/page.tsx
index f8789af2..2c3cef14 100644
--- a/src/app/master-data/area/page.tsx
+++ b/src/app/master-data/area/page.tsx
@@ -1,11 +1,7 @@
import AreasTable from '@/components/pages/master-data/area/AreasTable';
const Nonstock = () => {
- return (
-
- );
+ return ;
};
export default Nonstock;
diff --git a/src/app/master-data/bank/page.tsx b/src/app/master-data/bank/page.tsx
index 3f913c55..371cc3bf 100644
--- a/src/app/master-data/bank/page.tsx
+++ b/src/app/master-data/bank/page.tsx
@@ -1,11 +1,7 @@
import BanksTable from '@/components/pages/master-data/bank/BanksTable';
const Bank = () => {
- return (
-
- );
+ return ;
};
export default Bank;
diff --git a/src/app/master-data/customer/page.tsx b/src/app/master-data/customer/page.tsx
index 8aec1088..05c0e1e8 100644
--- a/src/app/master-data/customer/page.tsx
+++ b/src/app/master-data/customer/page.tsx
@@ -1,11 +1,7 @@
import CustomersTable from '@/components/pages/master-data/customer/CustomersTable';
const Customer = () => {
- return (
-
- );
+ return ;
};
export default Customer;
diff --git a/src/app/master-data/flock/page.tsx b/src/app/master-data/flock/page.tsx
index 76cc32c1..418018ab 100644
--- a/src/app/master-data/flock/page.tsx
+++ b/src/app/master-data/flock/page.tsx
@@ -1,11 +1,7 @@
import FlockTable from '@/components/pages/master-data/flock/FlocksTable';
const Flock = () => {
- return (
-
- );
+ return ;
};
export default Flock;
diff --git a/src/app/master-data/kandang/page.tsx b/src/app/master-data/kandang/page.tsx
index 293eb0da..e281e82c 100644
--- a/src/app/master-data/kandang/page.tsx
+++ b/src/app/master-data/kandang/page.tsx
@@ -1,11 +1,7 @@
import KandangsTable from '@/components/pages/master-data/kandang/KandangsTable';
const Nonstock = () => {
- return (
-
- );
+ return ;
};
export default Nonstock;
diff --git a/src/app/master-data/location/page.tsx b/src/app/master-data/location/page.tsx
index 338fdbff..af65761f 100644
--- a/src/app/master-data/location/page.tsx
+++ b/src/app/master-data/location/page.tsx
@@ -1,11 +1,7 @@
import LocationsTable from '@/components/pages/master-data/location/LocationsTable';
const Nonstock = () => {
- return (
-
- );
+ return ;
};
export default Nonstock;
diff --git a/src/app/master-data/nonstock/page.tsx b/src/app/master-data/nonstock/page.tsx
index 0812a5e2..02ed2e1e 100644
--- a/src/app/master-data/nonstock/page.tsx
+++ b/src/app/master-data/nonstock/page.tsx
@@ -1,11 +1,7 @@
import NonstocksTable from '@/components/pages/master-data/nonstock/NonstocksTable';
const Nonstock = () => {
- return (
-
- );
+ return ;
};
export default Nonstock;
diff --git a/src/app/master-data/product-category/page.tsx b/src/app/master-data/product-category/page.tsx
index 78a4fda3..7c0a6656 100644
--- a/src/app/master-data/product-category/page.tsx
+++ b/src/app/master-data/product-category/page.tsx
@@ -1,11 +1,7 @@
import ProductCategoryTable from '@/components/pages/master-data/product-category/ProductCategoryTable';
const ProductCategory = () => {
- return (
-
- );
+ return ;
};
export default ProductCategory;
diff --git a/src/app/master-data/product/page.tsx b/src/app/master-data/product/page.tsx
index a385d411..5a4aafa9 100644
--- a/src/app/master-data/product/page.tsx
+++ b/src/app/master-data/product/page.tsx
@@ -1,11 +1,7 @@
import ProductsTable from '@/components/pages/master-data/product/ProductTable';
const Product = () => {
- return (
-
- );
+ return ;
};
export default Product;
diff --git a/src/app/master-data/production-standard/page.tsx b/src/app/master-data/production-standard/page.tsx
index ed1107cd..17944ebe 100644
--- a/src/app/master-data/production-standard/page.tsx
+++ b/src/app/master-data/production-standard/page.tsx
@@ -1,11 +1,7 @@
import ProductionStandardTable from '@/components/pages/master-data/production-standard/ProductionStandardTable';
const ProductionStandardPage = () => {
- return (
-
- );
+ return ;
};
export default ProductionStandardPage;
diff --git a/src/app/master-data/supplier/page.tsx b/src/app/master-data/supplier/page.tsx
index 8000be0a..169fd071 100644
--- a/src/app/master-data/supplier/page.tsx
+++ b/src/app/master-data/supplier/page.tsx
@@ -1,11 +1,7 @@
import SuppliersTable from '@/components/pages/master-data/supplier/SupplierTable';
const Supplier = () => {
- return (
-
- );
+ return ;
};
export default Supplier;
diff --git a/src/app/master-data/uom/page.tsx b/src/app/master-data/uom/page.tsx
index 689b9d0d..b5ba52b8 100644
--- a/src/app/master-data/uom/page.tsx
+++ b/src/app/master-data/uom/page.tsx
@@ -1,11 +1,7 @@
import UomsTable from '@/components/pages/master-data/uom/UomsTable';
const Nonstock = () => {
- return (
-
- );
+ return ;
};
export default Nonstock;
diff --git a/src/app/master-data/warehouse/page.tsx b/src/app/master-data/warehouse/page.tsx
index eb5ae416..7119283e 100644
--- a/src/app/master-data/warehouse/page.tsx
+++ b/src/app/master-data/warehouse/page.tsx
@@ -1,11 +1,7 @@
import WarehousesTable from '@/components/pages/master-data/warehouse/WarehousesTable';
const Warehouse = () => {
- return (
-
- );
+ return ;
};
export default Warehouse;
diff --git a/src/components/pages/closing/ClosingsTable.tsx b/src/components/pages/closing/ClosingsTable.tsx
index 7885c75c..912e8dfd 100644
--- a/src/components/pages/closing/ClosingsTable.tsx
+++ b/src/components/pages/closing/ClosingsTable.tsx
@@ -351,19 +351,19 @@ const ClosingsTable = () => {
) : data.length === 0 ? (
-
- }
- title='Data Closing Belum Tersedia'
- subtitle='Tidak ada data closing untuk saat ini.'
- />
+
+
+ }
+ />
+
) : (
data={isResponseSuccess(closings) ? closings?.data : []}
@@ -382,10 +382,7 @@ const ClosingsTable = () => {
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
- containerClassName: cn('mt-3', {
- 'w-full mb-0':
- isResponseSuccess(closings) && closings?.data?.length === 0,
- }),
+ containerClassName: cn('mt-3 mb-0'),
headerColumnClassName: 'text-nowrap',
}}
/>
diff --git a/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx b/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx
index 4b59510a..c99a3194 100644
--- a/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx
+++ b/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx
@@ -6,13 +6,13 @@ import { ColumnDef } from '@tanstack/react-table';
const ClosingTableSkeleton = ({
columns,
icon,
- title,
- subtitle,
+ title = 'No Data Available',
+ subtitle = 'There is no closing data displayed. Enter closing data to get started.',
}: {
columns: ColumnDef[];
icon: React.ReactNode;
- title: string;
- subtitle: string;
+ title?: string;
+ subtitle?: string;
}) => {
return (
diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx
index e2e86535..849c1f83 100644
--- a/src/components/pages/expense/ExpensesTable.tsx
+++ b/src/components/pages/expense/ExpensesTable.tsx
@@ -25,6 +25,7 @@ import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWith
import RequirePermission from '@/components/helper/RequirePermission';
import ButtonFilter from '@/components/helper/ButtonFilter';
import ExpensesFilterModal from '@/components/pages/expense/filter/ExpensesFilterModal';
+import ExpenseTableSkeleton from '@/components/pages/expense/skeleton/ExpenseTableSkeleton';
import { Expense } from '@/types/api/expense';
import { ExpenseApi } from '@/services/api/expense';
@@ -692,30 +693,47 @@ const ExpensesTable = () => {
{/* Table Section */}
-
- data={isResponseSuccess(expenses) ? expenses?.data : []}
- columns={expensesColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(expenses) ? expenses?.meta?.page : 0}
- totalItems={
- isResponseSuccess(expenses) ? expenses?.meta?.total_results : 0
- }
- onPageChange={setPage}
- onPageSizeChange={setPageSize}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- rowSelection={rowSelection}
- setRowSelection={setRowSelection}
- enableRowSelection={tableEnableRowSelectionHandler}
- className={{
- containerClassName: cn('p-3 mb-0', {
- 'w-full':
- isResponseSuccess(expenses) && expenses?.data?.length === 0,
- }),
- headerColumnClassName: 'text-nowrap',
- }}
- />
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(expenses) || expenses.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={isResponseSuccess(expenses) ? expenses?.data : []}
+ columns={expensesColumns}
+ pageSize={tableFilterState.pageSize}
+ page={isResponseSuccess(expenses) ? expenses?.meta?.page : 0}
+ totalItems={
+ isResponseSuccess(expenses) ? expenses?.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ rowSelection={rowSelection}
+ setRowSelection={setRowSelection}
+ enableRowSelection={tableEnableRowSelectionHandler}
+ className={{
+ containerClassName: cn('p-3 mb-0'),
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
diff --git a/src/components/pages/expense/filter/ExpensesFilterModal.tsx b/src/components/pages/expense/filter/ExpensesFilterModal.tsx
index 99f5a75a..1885785f 100644
--- a/src/components/pages/expense/filter/ExpensesFilterModal.tsx
+++ b/src/components/pages/expense/filter/ExpensesFilterModal.tsx
@@ -121,37 +121,40 @@ const ExpensesFilterModal = ({
{/* Modal Body */}
-
-
-
- {formik.touched.realization_date &&
- formik.errors.realization_date && (
-
- {formik.errors.realization_date}
-
- )}
+
+
Tanggal
+
+
+
+
+
+ {formik.touched.realization_date &&
+ formik.errors.realization_date && (
+
+ {formik.errors.realization_date}
+
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default ExpenseTableSkeleton;
diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx
index b30308a5..100eecb4 100644
--- a/src/components/pages/finance/FinanceTable.tsx
+++ b/src/components/pages/finance/FinanceTable.tsx
@@ -1,10 +1,19 @@
-import { useEffect, useMemo, useRef, useState } from 'react';
-import { CellContext } from '@tanstack/react-table';
+'use client';
+
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import { CellContext, ColumnDef } from '@tanstack/react-table';
import useSWR from 'swr';
+import { Icon } from '@iconify/react';
import { useFormik } from 'formik';
+import { cn, formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
import Button from '@/components/Button';
-import Card from '@/components/Card';
import DateInput from '@/components/input/DateInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, {
@@ -12,7 +21,6 @@ import SelectInput, {
useSelect,
} from '@/components/input/SelectInput';
import Table from '@/components/Table';
-import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Finance } from '@/types/api/finance/finance';
import {
@@ -25,115 +33,145 @@ import { FinanceApi } from '@/services/api/finance';
import { isResponseSuccess } from '@/lib/api-helper';
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
import { Bank } from '@/types/api/master-data/bank';
-import { useModal } from '@/components/Modal';
+import Modal, { useModal } from '@/components/Modal';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import toast from 'react-hot-toast';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
-import { Icon } from '@iconify/react';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import { useUiStore } from '@/stores/ui/ui.store';
import {
FinanceTableFilterSchema,
FinanceTableFilterValues,
-} from './FinanceTableFilter.schema';
+} from '@/components/pages/finance/filter/FinanceFilter';
+import FinanceTableSkeleton from '@/components/pages/finance/skeleton/FinanceTableSkeleton';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `finance#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-finance#${props.row.original.id}`;
+
+ const closePopover = () => {
+ const popover = document.getElementById(popoverId) as
+ | HTMLDivElement
+ | undefined;
+ popover?.hidePopover?.();
+ };
+
return (
-
-
+
-
-
+
+
- {FINANCE_TRANSACTION_STATUS.includes(
- props.row.original.transaction_type
- ) && (
-
-
);
};
@@ -171,6 +209,9 @@ const FinanceTable = () => {
},
});
+ // ===== FILTER MODAL STATE =====
+ const filterModal = useModal();
+
// ===== State =====
const deleteModal = useModal();
const [selectedTransactionType, setSelectedTransactionType] = useState<
@@ -215,6 +256,18 @@ const FinanceTable = () => {
updateFilter('sortBy', values.sort_by);
updateFilter('startDate', values.start_date);
updateFilter('endDate', values.end_date);
+ filterModal.closeModal();
+ },
+ onReset: () => {
+ updateFilter('search', '');
+ resetSearchValue();
+ updateFilter('transactionTypes', '');
+ updateFilter('bankIds', '');
+ updateFilter('customerIds', '');
+ updateFilter('supplierIds', '');
+ updateFilter('sortBy', '');
+ updateFilter('startDate', '');
+ updateFilter('endDate', '');
},
});
@@ -267,10 +320,41 @@ const FinanceTable = () => {
});
}, [bankOptions, bankRawData]);
+ // ===== ACTIVE FILTERS COUNT =====
+ const activeFiltersCount = useMemo(() => {
+ let count = 0;
+
+ if (tableFilterState.transactionTypes) count += 1;
+ if (tableFilterState.bankIds) count += 1;
+ if (tableFilterState.customerIds) count += 1;
+ if (tableFilterState.supplierIds) count += 1;
+ if (tableFilterState.sortBy) count += 1;
+ if (tableFilterState.startDate) count += 1;
+ if (tableFilterState.endDate) count += 1;
+
+ return count;
+ }, [
+ tableFilterState.transactionTypes,
+ tableFilterState.bankIds,
+ tableFilterState.customerIds,
+ tableFilterState.supplierIds,
+ tableFilterState.sortBy,
+ tableFilterState.startDate,
+ tableFilterState.endDate,
+ ]);
+
+ const hasFilters = activeFiltersCount > 0;
+
// ===== Handler =====
- const searchChangeHandler = (e: React.ChangeEvent) => {
- filterFormik.setFieldValue('search', e.target.value);
- };
+ const searchChangeHandler = useCallback(
+ (e: React.ChangeEvent) => {
+ updateFilter('search', e.target.value);
+ setSearchValue(e.target.value);
+ setPage(1);
+ },
+ [updateFilter, setSearchValue, setPage]
+ );
+
const transactionTypeChangeHandler = (
val: OptionType | OptionType[] | null
) => {
@@ -384,6 +468,11 @@ const FinanceTable = () => {
}
};
+ const handleFilterModalOpen = () => {
+ filterModal.openModal();
+ filterFormik.validateForm();
+ };
+
const resetFilterHandler = () => {
setSelectedTransactionType(null);
setSelectedBank(null);
@@ -403,6 +492,7 @@ const FinanceTable = () => {
updateFilter('startDate', '');
updateFilter('endDate', '');
};
+
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -414,8 +504,8 @@ const FinanceTable = () => {
setIsDeleteLoading(false);
};
- const columns = useMemo(() => {
- return [
+ const columns: ColumnDef[] = useMemo(
+ () => [
{
header: 'ID',
accessorKey: 'payment_code',
@@ -495,32 +585,17 @@ const FinanceTable = () => {
};
return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
+
);
},
},
- ];
- }, []);
+ ],
+ []
+ );
useEffect(() => {
return () => {
@@ -552,152 +627,280 @@ const FinanceTable = () => {
}, [resetSearchValue, dateErrorShown]);
return (
-
-
-
-
- Injection Saldo Bank
-
-
-
-
- Saldo Awal
-
-
-
-
- Tambah
-
-
-
-
+ <>
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Injection (Saldo Bank)
+
+
+
+
+
+ Add Initial Balance
+
+
+
+
+
+ Add Finance
+
+
+
+
+ {/* Search and Filter */}
+
+
+ }
+ 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',
+ }}
+ />
+
- Reset
-
- filterFormik.handleSubmit()}
- >
- Cari
+
+ Filter
+ {hasFilters && (
+
+ {activeFiltersCount}
+
+ )}
- }
- >
-
-
-
-
-
-
-
-
-
-
-
- data={isResponseSuccess(finances) ? finances.data : []}
- columns={columns}
- pageSize={tableFilterState.pageSize}
- page={tableFilterState.page}
- onPageChange={setPage}
- onPageSizeChange={setPageSize}
- totalItems={
- isResponseSuccess(finances) ? finances.meta?.total_results : 0
- }
- isLoading={isLoading}
- />
+
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(finances) || finances.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={isResponseSuccess(finances) ? finances.data : []}
+ columns={columns}
+ pageSize={tableFilterState.pageSize}
+ page={tableFilterState.page}
+ totalItems={
+ isResponseSuccess(finances) ? finances.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ className={{
+ containerClassName: cn('p-3 mb-0'),
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
+
+
+ {/* Filter Modal */}
+
+ {/* Modal Header */}
+
+
+
+
Filter Data
+
+
+
+
+
+
+
+
{
onClick: confirmationModalDeleteClickHandler,
}}
/>
-
+ >
);
};
diff --git a/src/components/pages/finance/FinanceTableFilter.schema.ts b/src/components/pages/finance/filter/FinanceFilter.ts
similarity index 100%
rename from src/components/pages/finance/FinanceTableFilter.schema.ts
rename to src/components/pages/finance/filter/FinanceFilter.ts
diff --git a/src/components/pages/finance/skeleton/FinanceTableSkeleton.tsx b/src/components/pages/finance/skeleton/FinanceTableSkeleton.tsx
new file mode 100644
index 00000000..ccfbf1f5
--- /dev/null
+++ b/src/components/pages/finance/skeleton/FinanceTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { Finance } from '@/types/api/finance/finance';
+import { ColumnDef } from '@tanstack/react-table';
+
+const FinanceTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no finance data displayed. Enter finance data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default FinanceTableSkeleton;
diff --git a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx
index 0ae5034b..6d8a17e2 100644
--- a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx
+++ b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx
@@ -13,6 +13,7 @@ import { InventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import StatusBadge from '@/components/helper/StatusBadge';
+import InventoryAdjustmentTableSkeleton from '@/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton';
const InventoryAdjustmentTable = () => {
const {
@@ -192,38 +193,55 @@ const InventoryAdjustmentTable = () => {
{/* Table Section */}
-
- data={
- isResponseSuccess(inventoryAdjustments)
- ? inventoryAdjustments?.data
- : []
- }
- columns={inventoryAdjustmentsColumns}
- pageSize={tableFilterState.pageSize}
- page={
- isResponseSuccess(inventoryAdjustments)
- ? inventoryAdjustments?.meta?.page
- : 0
- }
- totalItems={
- isResponseSuccess(inventoryAdjustments)
- ? inventoryAdjustments?.meta?.total_results
- : 0
- }
- onPageChange={setPage}
- onPageSizeChange={setPageSize}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn('p-3 mb-0', {
- 'w-full':
- isResponseSuccess(inventoryAdjustments) &&
- inventoryAdjustments?.data?.length === 0,
- }),
- headerColumnClassName: 'text-nowrap',
- }}
- />
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(inventoryAdjustments) ||
+ inventoryAdjustments.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={
+ isResponseSuccess(inventoryAdjustments)
+ ? inventoryAdjustments?.data
+ : []
+ }
+ columns={inventoryAdjustmentsColumns}
+ pageSize={tableFilterState.pageSize}
+ page={
+ isResponseSuccess(inventoryAdjustments)
+ ? inventoryAdjustments?.meta?.page
+ : 0
+ }
+ totalItems={
+ isResponseSuccess(inventoryAdjustments)
+ ? inventoryAdjustments?.meta?.total_results
+ : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: cn('p-3 mb-0'),
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
);
diff --git a/src/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton.tsx b/src/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton.tsx
new file mode 100644
index 00000000..3473f996
--- /dev/null
+++ b/src/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
+import { ColumnDef } from '@tanstack/react-table';
+
+const InventoryAdjustmentTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no inventory adjustment data displayed. Enter inventory adjustment data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default InventoryAdjustmentTableSkeleton;
diff --git a/src/components/pages/inventory/movement/MovementTable.tsx b/src/components/pages/inventory/movement/MovementTable.tsx
index ab4f80d0..c85577de 100644
--- a/src/components/pages/inventory/movement/MovementTable.tsx
+++ b/src/components/pages/inventory/movement/MovementTable.tsx
@@ -16,6 +16,7 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import RequirePermission from '@/components/helper/RequirePermission';
import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent';
+import MovementTableSkeleton from '@/components/pages/inventory/movement/skeleton/MovementTableSkeleton';
const RowOptionsMenu = ({
popoverPosition = 'bottom',
@@ -198,27 +199,44 @@ const MovementTable = () => {
{/* Table Section */}
-
- data={isResponseSuccess(movements) ? movements?.data : []}
- columns={movementColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(movements) ? movements?.meta?.page : 0}
- totalItems={
- isResponseSuccess(movements) ? movements?.meta?.total_results : 0
- }
- onPageChange={setPage}
- onPageSizeChange={setPageSize}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn('p-3 mb-0', {
- 'w-full':
- isResponseSuccess(movements) && movements?.data?.length === 0,
- }),
- headerColumnClassName: 'text-nowrap',
- }}
- />
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(movements) || movements.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={isResponseSuccess(movements) ? movements?.data : []}
+ columns={movementColumns}
+ pageSize={tableFilterState.pageSize}
+ page={isResponseSuccess(movements) ? movements?.meta?.page : 0}
+ totalItems={
+ isResponseSuccess(movements) ? movements?.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: cn('p-3 mb-0'),
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
);
diff --git a/src/components/pages/inventory/movement/skeleton/MovementTableSkeleton.tsx b/src/components/pages/inventory/movement/skeleton/MovementTableSkeleton.tsx
new file mode 100644
index 00000000..a3ba3c5a
--- /dev/null
+++ b/src/components/pages/inventory/movement/skeleton/MovementTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { Movement } from '@/types/api/inventory/movement';
+import { ColumnDef } from '@tanstack/react-table';
+
+const MovementTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no movement data displayed. Enter movement data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default MovementTableSkeleton;
diff --git a/src/components/pages/inventory/product/InventoryProductTable.tsx b/src/components/pages/inventory/product/InventoryProductTable.tsx
index cfbc284a..53d8fcb3 100644
--- a/src/components/pages/inventory/product/InventoryProductTable.tsx
+++ b/src/components/pages/inventory/product/InventoryProductTable.tsx
@@ -15,6 +15,7 @@ import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent';
+import InventoryProductTableSkeleton from '@/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton';
const RowOptionsMenu = ({
popoverPosition = 'bottom',
@@ -206,36 +207,55 @@ const InventoryProductTable = () => {
{/* Table Section */}
-
- data={
- isResponseSuccess(inventoryProducts) ? inventoryProducts?.data : []
- }
- columns={columns}
- pageSize={tableFilterState.pageSize}
- page={
- isResponseSuccess(inventoryProducts)
- ? inventoryProducts?.meta?.page
- : 0
- }
- totalItems={
- isResponseSuccess(inventoryProducts)
- ? inventoryProducts?.meta?.total_results
- : 0
- }
- onPageChange={setPage}
- onPageSizeChange={setPageSize}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn('p-3 mb-0', {
- 'w-full':
- isResponseSuccess(inventoryProducts) &&
- inventoryProducts?.data?.length === 0,
- }),
- headerColumnClassName: 'text-nowrap',
- }}
- />
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(inventoryProducts) ||
+ inventoryProducts.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={
+ isResponseSuccess(inventoryProducts)
+ ? inventoryProducts?.data
+ : []
+ }
+ columns={columns}
+ pageSize={tableFilterState.pageSize}
+ page={
+ isResponseSuccess(inventoryProducts)
+ ? inventoryProducts?.meta?.page
+ : 0
+ }
+ totalItems={
+ isResponseSuccess(inventoryProducts)
+ ? inventoryProducts?.meta?.total_results
+ : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: cn('p-3 mb-0'),
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
);
diff --git a/src/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton.tsx b/src/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton.tsx
new file mode 100644
index 00000000..9fe9cb51
--- /dev/null
+++ b/src/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { InventoryProduct } from '@/types/api/inventory/product';
+import { ColumnDef } from '@tanstack/react-table';
+
+const InventoryProductTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no inventory product data displayed. Enter inventory product data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default InventoryProductTableSkeleton;
diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx
index de43ba68..540a3eca 100644
--- a/src/components/pages/marketing/MarketingTable.tsx
+++ b/src/components/pages/marketing/MarketingTable.tsx
@@ -31,6 +31,7 @@ import PopoverContent from '@/components/popover/PopoverContent';
import StatusBadge from '@/components/helper/StatusBadge';
import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter';
import ButtonFilter from '@/components/helper/ButtonFilter';
+import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton';
const RowsOptionsMenu = ({
props,
@@ -616,28 +617,49 @@ const MarketingTable = () => {
-
+
+ {isLoadingMarketing ? (
+
+
+
+ ) : !isResponseSuccess(marketing) || marketing.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default MarketingTableSkeleton;
diff --git a/src/components/pages/master-data/area/AreasTable.tsx b/src/components/pages/master-data/area/AreasTable.tsx
index d92c7840..1884dca3 100644
--- a/src/components/pages/master-data/area/AreasTable.tsx
+++ b/src/components/pages/master-data/area/AreasTable.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChangeEventHandler, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
@@ -11,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
import RequirePermission from '@/components/helper/RequirePermission';
+import AreaTableSkeleton from '@/components/pages/master-data/area/skeleton/AreaTableSkeleton';
import { Area } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `area#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-area#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -87,10 +108,17 @@ const AreasTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '' },
- paramMap: { page: 'page', pageSize: 'limit', nameSort: 'sort_name' },
+ initial: {
+ search: '',
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ },
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: areas,
isLoading,
@@ -101,65 +129,12 @@ const AreasTable = () => {
);
const deleteModal = useModal();
-
const [selectedArea, setSelectedArea] = useState(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const areasColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedArea(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -179,95 +154,132 @@ const AreasTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const areasColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedArea(props.row.original);
+ deleteModal.openModal();
+ };
- // track sorting
- useEffect(() => {
- const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
-
- if (!isNameSorted) {
- updateFilter('nameSort', '');
- } else {
- updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
- }
- }, [sorting, updateFilter]);
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Area
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(areas) ? areas?.data : []}
- columns={areasColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(areas) ? areas?.meta?.page : 0}
- totalItems={isResponseSuccess(areas) ? areas?.meta?.total_results : 0}
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20': isResponseSuccess(areas) && areas?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(areas) || areas.data?.length === 0 ? (
+
+ ) : (
+
+ data={isResponseSuccess(areas) ? areas?.data : []}
+ columns={areasColumns}
+ pageSize={tableFilterState.pageSize}
+ page={isResponseSuccess(areas) ? areas?.meta?.page : 0}
+ totalItems={
+ isResponseSuccess(areas) ? areas?.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default AreaTableSkeleton;
diff --git a/src/components/pages/master-data/bank/BanksTable.tsx b/src/components/pages/master-data/bank/BanksTable.tsx
index c5a564fe..a6a5dbef 100644
--- a/src/components/pages/master-data/bank/BanksTable.tsx
+++ b/src/components/pages/master-data/bank/BanksTable.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChangeEventHandler, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
@@ -11,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import BankTableSkeleton from '@/components/pages/master-data/bank/skeleton/BankTableSkeleton';
import { Bank } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `bank#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-bank#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -87,10 +108,17 @@ const BanksTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '' },
- paramMap: { page: 'page', pageSize: 'limit', nameSort: 'sort_name' },
+ initial: {
+ search: '',
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ },
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: banks,
isLoading,
@@ -101,78 +129,12 @@ const BanksTable = () => {
);
const deleteModal = useModal();
-
const [selectedBank, setSelectedBank] = useState(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const banksColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'alias',
- header: 'Alias',
- },
- {
- accessorKey: 'account_number',
- header: 'No. Rekening',
- },
- {
- accessorKey: 'owner',
- header: 'Pemilik',
- cell: (props) => (props.getValue() ? props.getValue() : '-'),
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedBank(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -192,93 +154,145 @@ const BanksTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const banksColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'alias',
+ header: 'Alias',
+ },
+ {
+ accessorKey: 'account_number',
+ header: 'No. Rekening',
+ },
+ {
+ accessorKey: 'owner',
+ header: 'Pemilik',
+ cell: (props) => props.getValue() || '-',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedBank(props.row.original);
+ deleteModal.openModal();
+ };
- // track sorting
- useEffect(() => {
- const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
-
- if (!isNameSorted) {
- updateFilter('nameSort', '');
- } else {
- updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
- }
- }, [sorting]);
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Bank
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(banks) ? banks?.data : []}
- columns={banksColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(banks) ? banks?.meta?.page : 0}
- totalItems={isResponseSuccess(banks) ? banks?.meta?.total_results : 0}
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20': isResponseSuccess(banks) && banks?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(banks) || banks.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={isResponseSuccess(banks) ? banks?.data : []}
+ columns={banksColumns}
+ pageSize={tableFilterState.pageSize}
+ page={isResponseSuccess(banks) ? banks?.meta?.page : 0}
+ totalItems={
+ isResponseSuccess(banks) ? banks?.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={isLoading}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default BankTableSkeleton;
diff --git a/src/components/pages/master-data/customer/CustomersTable.tsx b/src/components/pages/master-data/customer/CustomersTable.tsx
index e605d9f7..2768daa3 100644
--- a/src/components/pages/master-data/customer/CustomersTable.tsx
+++ b/src/components/pages/master-data/customer/CustomersTable.tsx
@@ -1,77 +1,102 @@
'use client';
-import Button from '@/components/Button';
+import { ChangeEventHandler, useMemo, useState } from 'react';
+import useSWR from 'swr';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
+import toast from 'react-hot-toast';
+
+import { Icon } from '@iconify/react';
+import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
+import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import Table from '@/components/Table';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
-import { ROWS_OPTIONS } from '@/config/constant';
-import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { cn } from '@/lib/helper';
-import { CustomerApi } from '@/services/api/master-data';
-import { useTableFilter } from '@/services/hooks/useTableFilter';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import CustomerTableSkeleton from '@/components/pages/master-data/customer/skeleton/CustomerTableSkeleton';
+
import { Customer } from '@/types/api/master-data/customer';
-import { Icon } from '@iconify/react';
-import { CellContext, ColumnDef } from '@tanstack/react-table';
-import { useState } from 'react';
-import toast from 'react-hot-toast';
-import useSWR from 'swr';
+import { CustomerApi } from '@/services/api/master-data';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `customer#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-customer#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
-
-
-
- Edit
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -83,16 +108,17 @@ const CustomersTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '', picSort: '' },
+ initial: {
+ search: '',
+ },
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- picSort: 'sort_pic',
},
});
- // Fetch Data
+ const [sorting, setSorting] = useState([]);
+
const {
data: customers,
isLoading,
@@ -102,87 +128,16 @@ const CustomersTable = () => {
CustomerApi.getAllFetcher
);
- // State
const deleteModal = useModal();
const [selectedCustomer, setSelectedCustomer] = useState<
Customer | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- // Columns Definition
- const customersColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'pic',
- header: 'PIC',
- cell: (props) => props.row.original.pic.name,
- },
- {
- accessorKey: 'type',
- header: 'Type',
- cell: (props) => props.row.original.type,
- },
- {
- accessorKey: 'phone',
- header: 'Phone',
- },
- {
- accessorKey: 'email',
- header: 'Email',
- },
- {
- header: 'Aksi',
- 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 searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
- const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
-
- const deleteClickHandler = () => {
- setSelectedCustomer(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
-
- // Handler
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -202,81 +157,147 @@ const CustomersTable = () => {
toast.success('Successfully delete Customer!');
setIsDeleteLoading(false);
};
- const searchChangeHandler = (e: React.ChangeEvent) => {
- updateFilter('search', e.target.value);
- };
- const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
- setPageSize(newVal.value as number);
- };
+
+ const customersColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorFn: (row) => row.pic?.name ?? '-',
+ header: 'PIC',
+ },
+ {
+ accessorKey: 'type',
+ header: 'Type',
+ },
+ {
+ accessorKey: 'phone',
+ header: 'Phone',
+ },
+ {
+ accessorKey: 'email',
+ header: 'Email',
+ },
+ {
+ 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;
+
+ const deleteClickHandler = () => {
+ setSelectedCustomer(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Customer
+
+
-
-
+
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(customers) ? customers?.data : []}
- columns={customersColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(customers) ? customers?.meta?.page : 0}
- totalItems={
- isResponseSuccess(customers) ? customers?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(customers) && customers?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(customers) || customers.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={customers?.data}
+ columns={customersColumns}
+ pageSize={tableFilterState.pageSize}
+ page={customers?.meta?.page ?? 0}
+ totalItems={customers?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default CustomerTableSkeleton;
diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx
index dd6ebfe8..3550a346 100644
--- a/src/components/pages/master-data/flock/FlocksTable.tsx
+++ b/src/components/pages/master-data/flock/FlocksTable.tsx
@@ -1,87 +1,102 @@
'use client';
-import { CellContext, ColumnDef } from '@tanstack/react-table';
-import { Flock } from '@/types/api/master-data/flock';
-import { cn } from '@/lib/helper';
-import Button from '@/components/Button';
-import { Icon } from '@iconify/react';
-import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import { FlockApi } from '@/services/api/master-data';
-import { useModal } from '@/components/Modal';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
-import RequirePermission from '@/components/helper/RequirePermission';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
-import DebouncedTextInput from '@/components/input/DebouncedTextInput';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import { ROWS_OPTIONS } from '@/config/constant';
-import Table from '@/components/Table';
-import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import ConfirmationModal from '@/components/modal/ConfirmationModal';
-const RowsOptions = ({
- type = 'dropdown',
+import { Icon } from '@iconify/react';
+import Table from '@/components/Table';
+import DebouncedTextInput from '@/components/input/DebouncedTextInput';
+import Button from '@/components/Button';
+import { useModal } from '@/components/Modal';
+import ConfirmationModal from '@/components/modal/ConfirmationModal';
+import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import FlockTableSkeleton from '@/components/pages/master-data/flock/skeleton/FlockTableSkeleton';
+
+import { Flock } from '@/types/api/master-data/flock';
+import { FlockApi } from '@/services/api/master-data';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
+
+const RowOptionsMenu = ({
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `flock#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-flock#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
-
-
-
- Edit
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -93,15 +108,17 @@ const FlockTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '' },
+ initial: {
+ search: '',
+ },
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
},
});
- // Fetch Data
+ const [sorting, setSorting] = useState([]);
+
const {
data: flocks,
isLoading,
@@ -111,74 +128,16 @@ const FlockTable = () => {
FlockApi.getAllFetcher
);
- // State
const deleteModal = useModal();
const [selectedFlock, setSelectedFlock] = useState(
undefined
);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- // Columns Definition
- const flocksColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'created_at',
- header: 'Dibuat pada',
- cell: (props) =>
- new Date(props.row.original.created_at).toLocaleDateString(),
- },
- {
- header: 'Aksi',
- 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 searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
- const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
-
- const deleteClickHandler = () => {
- setSelectedFlock(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
-
- // Handler
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -196,85 +155,143 @@ const FlockTable = () => {
toast.success('Successfully delete Flock!');
setIsDeleteLoading(false);
};
- const searchChangeHandler = (e: React.ChangeEvent) => {
- updateFilter('search', e.target.value);
- };
- const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
- setPageSize(newVal.value as number);
- };
+
+ const flocksColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'created_at',
+ header: 'Dibuat pada',
+ cell: (props) =>
+ new Date(props.row.original.created_at).toLocaleDateString('id-ID'),
+ },
+ {
+ 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;
+
+ const deleteClickHandler = () => {
+ setSelectedFlock(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Flock
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(flocks) ? flocks?.data : []}
- columns={flocksColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(flocks) ? flocks?.meta?.page : 0}
- totalItems={
- isResponseSuccess(flocks) ? flocks?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- className={{
- containerClassName: cn({
- 'mb-20': isResponseSuccess(flocks) && flocks?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(flocks) || flocks.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={flocks?.data}
+ columns={flocksColumns}
+ pageSize={tableFilterState.pageSize}
+ page={flocks?.meta?.page ?? 0}
+ totalItems={flocks?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default FlockTableSkeleton;
diff --git a/src/components/pages/master-data/kandang/KandangsTable.tsx b/src/components/pages/master-data/kandang/KandangsTable.tsx
index 7d79d456..63e9fa58 100644
--- a/src/components/pages/master-data/kandang/KandangsTable.tsx
+++ b/src/components/pages/master-data/kandang/KandangsTable.tsx
@@ -1,13 +1,8 @@
'use client';
-import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import {
- CellContext,
- ColumnDef,
- ColumnSort,
- SortingState,
-} from '@tanstack/react-table';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
@@ -16,71 +11,93 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import KandangTableSkeleton from '@/components/pages/master-data/kandang/skeleton/KandangTableSkeleton';
import { Kandang } from '@/types/api/master-data/kandang';
import { KandangApi } from '@/services/api/master-data';
-import { cn, formatNumber } from '@/lib/helper';
+import { formatNumber } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `kandang#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-kandang#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -94,21 +111,15 @@ const KandangsTable = () => {
} = useTableFilter({
initial: {
search: '',
- nameSort: '',
- locationSort: '',
- capacitySort: '',
- picSort: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- locationSort: 'sort_location',
- capacitySort: 'sort_capacity',
- picSort: ' sort_pic',
},
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: kandangs,
isLoading,
@@ -119,82 +130,14 @@ const KandangsTable = () => {
);
const deleteModal = useModal();
-
const [selectedKandang, setSelectedKandang] = useState(
undefined
);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const kandangsColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'location',
- header: 'Lokasi',
- cell: (props) => props.row.original.location.name,
- },
- {
- accessorKey: 'capacity',
- header: 'Kapasitas',
- cell: (props) => formatNumber(props.row.original.capacity ?? 0),
- },
- {
- accessorKey: 'pic',
- header: 'PIC',
- cell: (props) => props.row.original.pic.name,
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedKandang(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -216,114 +159,143 @@ const KandangsTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const kandangsColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorFn: (row) => row.location?.name ?? '-',
+ header: 'Lokasi',
+ },
+ {
+ accessorKey: 'capacity',
+ header: 'Kapasitas',
+ cell: (props) => formatNumber(props.row.original.capacity ?? 0),
+ },
+ {
+ accessorFn: (row) => row.pic?.name ?? '-',
+ header: 'PIC',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedKandang(props.row.original);
+ deleteModal.openModal();
+ };
- const updateSortingFilter = useCallback(
- (
- sortName: Exclude,
- sortFilter: ColumnSort | undefined
- ) => {
- if (!sortFilter) {
- updateFilter(sortName, '');
- } else {
- updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc');
- }
- },
- [updateFilter]
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
);
- // track sorting
- useEffect(() => {
- const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name');
- const locationSortFilter = sorting.find(
- (sortItem) => sortItem.id === 'location'
- );
- const picSortFilter = sorting.find((sortItem) => sortItem.id === 'pic');
-
- updateSortingFilter('nameSort', nameSortFilter);
- updateSortingFilter('locationSort', locationSortFilter);
- updateSortingFilter('picSort', picSortFilter);
- }, [sorting, updateSortingFilter]);
-
return (
<>
-
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Kandang
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(kandangs) ? kandangs?.data : []}
- columns={kandangsColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(kandangs) ? kandangs?.meta?.page : 0}
- totalItems={
- isResponseSuccess(kandangs) ? kandangs?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(kandangs) && kandangs?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(kandangs) || kandangs.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={kandangs?.data}
+ columns={kandangsColumns}
+ pageSize={tableFilterState.pageSize}
+ page={kandangs?.meta?.page ?? 0}
+ totalItems={kandangs?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default KandangTableSkeleton;
diff --git a/src/components/pages/master-data/location/LocationsTable.tsx b/src/components/pages/master-data/location/LocationsTable.tsx
index a35ffd09..0b619079 100644
--- a/src/components/pages/master-data/location/LocationsTable.tsx
+++ b/src/components/pages/master-data/location/LocationsTable.tsx
@@ -1,13 +1,8 @@
'use client';
-import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import {
- CellContext,
- ColumnDef,
- ColumnSort,
- SortingState,
-} from '@tanstack/react-table';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
@@ -16,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import LocationTableSkeleton from '@/components/pages/master-data/location/skeleton/LocationTableSkeleton';
import { Location } from '@/types/api/master-data/location';
import { LocationApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `location#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-location#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -92,16 +108,17 @@ const LocationsTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '', addressSort: '', areaSort: '' },
+ initial: {
+ search: '',
+ },
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- addressSort: 'sort_address',
- areaSort: ' sort_area',
},
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: locations,
isLoading,
@@ -112,76 +129,14 @@ const LocationsTable = () => {
);
const deleteModal = useModal();
-
const [selectedLocation, setSelectedLocation] = useState<
Location | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const locationsColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'address',
- header: 'Alamat',
- },
- {
- accessorKey: 'area',
- header: 'Area',
- cell: (props) => props.row.original.area.name,
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedLocation(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -203,114 +158,138 @@ const LocationsTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const locationsColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'address',
+ header: 'Alamat',
+ },
+ {
+ accessorFn: (row) => row.area?.name ?? '-',
+ header: 'Area',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedLocation(props.row.original);
+ deleteModal.openModal();
+ };
- const updateSortingFilter = useCallback(
- (
- sortName: Exclude,
- sortFilter: ColumnSort | undefined
- ) => {
- if (!sortFilter) {
- updateFilter(sortName, '');
- } else {
- updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc');
- }
- },
- [updateFilter]
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
);
- // track sorting
- useEffect(() => {
- const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name');
- const addressSortFilter = sorting.find(
- (sortItem) => sortItem.id === 'address'
- );
- const areaSortFilter = sorting.find((sortItem) => sortItem.id === 'area');
-
- updateSortingFilter('nameSort', nameSortFilter);
- updateSortingFilter('addressSort', addressSortFilter);
- updateSortingFilter('areaSort', areaSortFilter);
- }, [sorting, updateSortingFilter]);
-
return (
<>
-
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Location
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(locations) ? locations?.data : []}
- columns={locationsColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(locations) ? locations?.meta?.page : 0}
- totalItems={
- isResponseSuccess(locations) ? locations?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(locations) && locations?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(locations) || locations.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={locations?.data}
+ columns={locationsColumns}
+ pageSize={tableFilterState.pageSize}
+ page={locations?.meta?.page ?? 0}
+ totalItems={locations?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default LocationTableSkeleton;
diff --git a/src/components/pages/master-data/nonstock/NonstocksTable.tsx b/src/components/pages/master-data/nonstock/NonstocksTable.tsx
index 6aeb3f99..8f15e529 100644
--- a/src/components/pages/master-data/nonstock/NonstocksTable.tsx
+++ b/src/components/pages/master-data/nonstock/NonstocksTable.tsx
@@ -1,13 +1,8 @@
'use client';
-import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import {
- CellContext,
- ColumnDef,
- ColumnSort,
- SortingState,
-} from '@tanstack/react-table';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
@@ -16,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import NonstockTableSkeleton from '@/components/pages/master-data/nonstock/skeleton/NonstockTableSkeleton';
import { Nonstock } from '@/types/api/master-data/nonstock';
import { NonstockApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `nonstock#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-nonstock#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -92,16 +108,17 @@ const NonstocksTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '', locationSort: '', picSort: '' },
+ initial: {
+ search: '',
+ },
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- locationSort: 'sort_location',
- picSort: ' sort_pic',
},
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: nonstocks,
isLoading,
@@ -112,88 +129,14 @@ const NonstocksTable = () => {
);
const deleteModal = useModal();
-
const [selectedNonstock, setSelectedNonstock] = useState<
Nonstock | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const nonstocksColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'uom',
- header: 'UOM',
- cell: (props) => props.row.original.uom.name,
- },
- {
- accessorKey: 'suppliers',
- header: 'Supplier',
- cell: (props) => {
- const supplierNames = props.row.original.suppliers.map(
- (supplier) => supplier.name
- );
-
- return supplierNames.join(', ') || '-';
- },
- },
- {
- accessorKey: 'flags',
- header: 'Flag',
- cell: (props) => props.row.original.flags?.join(', ') || '-',
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedNonstock(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -215,112 +158,143 @@ const NonstocksTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const nonstocksColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorFn: (row) => row.uom?.name ?? '-',
+ header: 'UOM',
+ },
+ {
+ accessorFn: (row) =>
+ row.suppliers?.map((supplier) => supplier.name).join(', ') || '-',
+ header: 'Supplier',
+ },
+ {
+ accessorFn: (row) => row.flags?.join(', ') || '-',
+ header: 'Flag',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedNonstock(props.row.original);
+ deleteModal.openModal();
+ };
- const updateSortingFilter = useCallback(
- (
- sortName: Exclude,
- sortFilter: ColumnSort | undefined
- ) => {
- if (!sortFilter) {
- updateFilter(sortName, '');
- } else {
- updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc');
- }
- },
- [updateFilter]
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
);
- // track sorting
- useEffect(() => {
- const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name');
- const locationSortFilter = sorting.find(
- (sortItem) => sortItem.id === 'location'
- );
- const picSortFilter = sorting.find((sortItem) => sortItem.id === 'pic');
-
- updateSortingFilter('nameSort', nameSortFilter);
- updateSortingFilter('locationSort', locationSortFilter);
- updateSortingFilter('picSort', picSortFilter);
- }, [sorting]);
-
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Nonstock
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(nonstocks) ? nonstocks?.data : []}
- columns={nonstocksColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(nonstocks) ? nonstocks?.meta?.page : 0}
- totalItems={
- isResponseSuccess(nonstocks) ? nonstocks?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(nonstocks) && nonstocks?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(nonstocks) || nonstocks.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={nonstocks?.data}
+ columns={nonstocksColumns}
+ pageSize={tableFilterState.pageSize}
+ page={nonstocks?.meta?.page ?? 0}
+ totalItems={nonstocks?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
{
label: supplier.name,
})) ?? [],
- flags: initialValues?.flags ?? [],
+ flags: initialValues?.flags?.includes('EKSPEDISI') ?? false,
};
}, [initialValues]);
@@ -112,7 +112,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
name: values.name,
uom_id: values.uomId,
supplier_ids: values.supplierIds as number[],
- flags: values.flags as flags[],
+ flags: values.flags ? ['EKSPEDISI'] : [],
};
switch (type) {
@@ -183,12 +183,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
router.push('/master-data/nonstock');
};
- const flagsChangeHandler = (val: OptionType | OptionType[] | null) => {
- const formattedFlags = (val as OptionType[]).map(
- (flag) => flag.value as string
- );
-
- formik.setFieldValue('flags', formattedFlags);
+ const expeditionChangeHandler = (e: React.ChangeEvent) => {
+ formik.setFieldValue('flags', e.target.value === 'true');
};
useEffect(() => {
@@ -268,18 +264,19 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
isDisabled={type === 'detail'}
/>
-
- formik.values.flags?.includes(opt.value)
- )}
- onChange={flagsChangeHandler}
- options={SUPPLIER_FLAG_OPTIONS}
+
diff --git a/src/components/pages/master-data/nonstock/skeleton/NonstockTableSkeleton.tsx b/src/components/pages/master-data/nonstock/skeleton/NonstockTableSkeleton.tsx
new file mode 100644
index 00000000..b3801a75
--- /dev/null
+++ b/src/components/pages/master-data/nonstock/skeleton/NonstockTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { Nonstock } from '@/types/api/master-data/nonstock';
+import { ColumnDef } from '@tanstack/react-table';
+
+const NonstockTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no nonstock data displayed. Enter nonstock data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default NonstockTableSkeleton;
diff --git a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx
index 11199c73..161ebed4 100644
--- a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx
+++ b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx
@@ -1,6 +1,12 @@
'use client';
-import { ChangeEventHandler, useEffect, useRef, useState } from 'react';
+import {
+ ChangeEventHandler,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
@@ -11,72 +17,93 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import ProductCategoryTableSkeleton from '@/components/pages/master-data/product-category/skeleton/ProductCategoryTableSkeleton';
import { ProductCategory } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `product-category#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-product-category#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -91,10 +118,17 @@ const ProductCategoryTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: searchValue, nameSort: '' },
- paramMap: { page: 'page', pageSize: 'limit', nameSort: 'sort_name' },
+ initial: {
+ search: searchValue,
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ },
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: productCategories,
isLoading,
@@ -105,71 +139,15 @@ const ProductCategoryTable = () => {
);
const deleteModal = useModal();
-
const [selectedProductCategory, setSelectedProductCategory] = useState<
ProductCategory | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const productCategoryColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'code',
- header: 'Code',
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedProductCategory(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ setSearchValue(e.target.value);
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -191,15 +169,51 @@ const ProductCategoryTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- setSearchValue(e.target.value);
- updateFilter('search', e.target.value);
- };
+ const productCategoryColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'code',
+ header: 'Code',
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
- setPageSize(newVal.value as number);
- };
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
+
+ const deleteClickHandler = () => {
+ setSelectedProductCategory(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
useEffect(() => {
// Store current path on mount
@@ -223,91 +237,91 @@ const ProductCategoryTable = () => {
};
}, [resetSearchValue]);
- useEffect(() => {
- const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
- if (!isNameSorted) {
- updateFilter('nameSort', '');
- } else {
- updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
- }
- }, [sorting, updateFilter]);
-
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Product Category
+
+
+
+
+ {/* Search */}
+
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={
- isResponseSuccess(productCategories) ? productCategories?.data : []
- }
- columns={productCategoryColumns}
- pageSize={tableFilterState.pageSize}
- page={
- isResponseSuccess(productCategories)
- ? productCategories?.meta?.page
- : 0
- }
- totalItems={
- isResponseSuccess(productCategories)
- ? productCategories?.meta?.total_results
- : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(productCategories) &&
- productCategories?.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',
- }}
- />
+
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(productCategories) ||
+ productCategories.data?.length === 0 ? (
+
+ ) : (
+
+ data={productCategories?.data}
+ columns={productCategoryColumns}
+ pageSize={tableFilterState.pageSize}
+ page={productCategories?.meta?.page ?? 0}
+ totalItems={productCategories?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default ProductCategoryTableSkeleton;
diff --git a/src/components/pages/master-data/product/ProductTable.tsx b/src/components/pages/master-data/product/ProductTable.tsx
index 74137a14..08b585f0 100644
--- a/src/components/pages/master-data/product/ProductTable.tsx
+++ b/src/components/pages/master-data/product/ProductTable.tsx
@@ -1,13 +1,8 @@
'use client';
-import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import {
- CellContext,
- ColumnDef,
- ColumnSort,
- SortingState,
-} from '@tanstack/react-table';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
@@ -16,69 +11,95 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import ProductTableSkeleton from '@/components/pages/master-data/product/skeleton/ProductTableSkeleton';
import { Product } from '@/types/api/master-data/product';
import { ProductApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
+import { formatCurrency } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
-}) => (
-
-
- {
+ const popoverId = `product#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-product#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
+ return (
+
+
-
- Detail
-
-
-
-
+
+
+
-
- Edit
-
-
-
-
-
- Delete
-
-
-
-);
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
+ );
+};
const ProductsTable = () => {
const {
@@ -90,21 +111,15 @@ const ProductsTable = () => {
} = useTableFilter({
initial: {
search: '',
- nameSort: '',
- skuSort: '',
- brandSort: '',
- categorySort: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- skuSort: 'sort_sku',
- brandSort: 'sort_brand',
- categorySort: 'sort_category',
},
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: products,
isLoading,
@@ -119,114 +134,10 @@ const ProductsTable = () => {
undefined
);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
- const productsColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'sku',
- header: 'SKU',
- },
- {
- accessorKey: 'brand',
- header: 'Merek',
- },
- {
- accessorKey: 'product_category',
- header: 'Kategori',
- cell: (props) => props.row.original.product_category?.name ?? '-',
- },
- {
- accessorKey: 'uom',
- header: 'Satuan',
- cell: (props) => props.row.original.uom?.name ?? '-',
- },
- {
- accessorKey: 'product_price',
- header: 'Harga Produk',
- cell: (props) =>
- props.row.original.product_price?.toLocaleString() ?? '-',
- },
- {
- accessorKey: 'selling_price',
- header: 'Harga Jual',
- cell: (props) =>
- props.row.original.selling_price?.toLocaleString() ?? '-',
- },
- {
- accessorKey: 'tax',
- header: 'Pajak (%)',
- cell: (props) => props.row.original.tax ?? '-',
- },
- {
- accessorKey: 'expiry_period',
- header: 'Kadaluarsa (hari)',
- cell: (props) => props.row.original.expiry_period ?? '-',
- },
- {
- accessorKey: 'suppliers',
- header: 'Supplier',
- cell: (props) =>
- props.row.original.suppliers?.map((s) => s.name).join(', ') || '-',
- },
- {
- accessorKey: 'flags',
- header: 'Flags',
- cell: (props) =>
- props.row.original.flags?.length
- ? props.row.original.flags.join(', ')
- : '-',
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedProduct(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -248,110 +159,190 @@ const ProductsTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const productsColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'sku',
+ header: 'SKU',
+ },
+ {
+ accessorKey: 'brand',
+ header: 'Merek',
+ },
+ {
+ accessorFn: (row) => row.product_category?.name ?? '-',
+ header: 'Kategori',
+ },
+ {
+ accessorFn: (row) => row.uom?.name ?? '-',
+ header: 'Satuan',
+ },
+ {
+ accessorKey: 'product_price',
+ header: 'Harga Produk',
+ cell: (props) =>
+ props.row.original.product_price
+ ? formatCurrency(props.row.original.product_price)
+ : '-',
+ },
+ {
+ accessorKey: 'selling_price',
+ header: 'Harga Jual',
+ cell: (props) =>
+ props.row.original.selling_price
+ ? formatCurrency(props.row.original.selling_price)
+ : '-',
+ },
+ {
+ accessorKey: 'tax',
+ header: 'Pajak (%)',
+ cell: (props) => props.row.original.tax ?? '-',
+ },
+ {
+ accessorKey: 'expiry_period',
+ header: 'Kadaluarsa (hari)',
+ cell: (props) => props.row.original.expiry_period ?? '-',
+ },
+ {
+ accessorFn: (row) =>
+ row.suppliers?.map((s) => s.name).join(', ') || '-',
+ header: 'Supplier',
+ },
+ {
+ accessorKey: 'flag',
+ header: 'Flag',
+ cell: (props) =>
+ props.row.original.flag ? props.row.original.flag : '-',
+ },
+ {
+ accessorFn: (row) =>
+ row.sub_flags?.length ? row.sub_flags.join(', ') : '-',
+ header: 'Kategori Flags',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
- setPageSize(newVal.value as number);
- };
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- const updateSortingFilter = useCallback(
- (
- sortName: Exclude,
- sortFilter: ColumnSort | undefined
- ) => {
- if (!sortFilter) {
- updateFilter(sortName, '');
- } else {
- updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc');
- }
- },
- [updateFilter]
+ const deleteClickHandler = () => {
+ setSelectedProduct(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
);
- useEffect(() => {
- const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name');
- const skuSortFilter = sorting.find((sortItem) => sortItem.id === 'sku');
- const brandSortFilter = sorting.find((sortItem) => sortItem.id === 'brand');
- const categorySortFilter = sorting.find(
- (sortItem) => sortItem.id === 'product_category'
- );
-
- updateSortingFilter('nameSort', nameSortFilter);
- updateSortingFilter('skuSort', skuSortFilter);
- updateSortingFilter('brandSort', brandSortFilter);
- updateSortingFilter('categorySort', categorySortFilter);
- }, [sorting, updateSortingFilter]);
-
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Product
+
+
+
+
+ {/* Search */}
+
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(products) ? products?.data : []}
- columns={productsColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(products) ? products?.meta?.page : 0}
- totalItems={
- isResponseSuccess(products) ? products?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(products) && products?.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',
- }}
- />
+
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(products) || products.data?.length === 0 ? (
+
+ ) : (
+
+ data={products?.data}
+ columns={productsColumns}
+ pageSize={tableFilterState.pageSize}
+ page={products?.meta?.page ?? 0}
+ totalItems={products?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
+
=
@@ -94,10 +96,26 @@ export const ProductFormSchema: Yup.ObjectSchema =
)
.required('Supplier wajib diisi!'),
- flags: Yup.array()
+ flag: Yup.string()
+ .min(1, 'Flag wajib diisi!')
+ .required('Flag wajib diisi!')
+ .typeError('Flag wajib diisi!'),
+
+ sub_flags: Yup.array()
.of(Yup.string().required())
- .min(1, 'Minimal harus ada 1 flag!')
- .required('Flag wajib diisi!'),
+ .when('flag', {
+ is: (flag: string) => {
+ const mapping = PRODUCT_FLAG_MAPPING.options.find(
+ (opt) => opt.flag.value === flag
+ );
+ return mapping?.allow_without_sub_flag === false;
+ },
+ then: (schema) =>
+ schema
+ .required('Sub flag wajib diisi!')
+ .min(1, 'Sub flag wajib diisi!'),
+ otherwise: (schema) => schema,
+ }),
});
export const UpdateProductFormSchema = ProductFormSchema;
diff --git a/src/components/pages/master-data/product/form/ProductForm.tsx b/src/components/pages/master-data/product/form/ProductForm.tsx
index 65329464..01fa192c 100644
--- a/src/components/pages/master-data/product/form/ProductForm.tsx
+++ b/src/components/pages/master-data/product/form/ProductForm.tsx
@@ -36,8 +36,16 @@ import {
ProductApi,
} from '@/services/api/master-data';
import { cn } from '@/lib/helper';
-import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
+
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
+import { ConstantsApi } from '@/services/api/constants/constants';
+import type {
+ TransformedConstants,
+ ProductFlagMapping,
+} from '@/types/api/constants/constants';
+import useSWR from 'swr';
+import { PRODUCT_FLAG_MAPPING } from '@/config/constant';
+
import { Supplier } from '@/types/api/master-data/supplier';
import Card from '@/components/Card';
import { removeArrayItemAndSync } from '@/lib/utils/formik';
@@ -53,6 +61,24 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const [productFormErrorMessage, setProductFormErrorMessage] = useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
+ const {
+ data: constants,
+ error: constantsError,
+ isLoading: isLoadingConstants,
+ } = useSWR(
+ 'constants',
+ ConstantsApi.fetchTransformedConstants.bind(ConstantsApi),
+ {
+ shouldRetryOnError: false,
+ }
+ );
+
+ const productFlagMapping: ProductFlagMapping | null = useMemo(() => {
+ if (constantsError || !constants?.product_flag_mapping) {
+ return PRODUCT_FLAG_MAPPING as unknown as ProductFlagMapping;
+ }
+ return constants.product_flag_mapping;
+ }, [constants, constantsError]);
const createProductHandler = useCallback(
async (payload: CreateProductPayload) => {
@@ -110,7 +136,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
price: supplier.price,
}))
: [],
- flags: initialValues?.flags ?? [],
+ flag: initialValues?.flag ?? '',
+ sub_flags: initialValues?.sub_flags ?? [],
}),
[initialValues]
);
@@ -139,7 +166,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
supplier_id: s.supplier?.value as number,
price: parseInt(s.price.toString()) || 0,
})),
- flags: values.flags.filter((f): f is string => typeof f === 'string'),
+ flag: values.flag,
+ sub_flags: values.sub_flags,
};
switch (type) {
case 'add':
@@ -200,6 +228,28 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
});
}, [supplierOptions, formik.values.suppliers]);
+ const selectedFlagMapping = useMemo(() => {
+ return productFlagMapping?.options.find(
+ (opt) => opt.flag.value === formik.values.flag
+ );
+ }, [formik.values.flag, productFlagMapping]);
+
+ const subFlagOptions = useMemo(() => {
+ return selectedFlagMapping?.sub_flags ?? [];
+ }, [selectedFlagMapping]);
+
+ const selectedSubFlagValues = useMemo(() => {
+ return (
+ selectedFlagMapping?.sub_flags.filter((subFlag) =>
+ formik.values.sub_flags?.includes(subFlag.value as string)
+ ) ?? []
+ );
+ }, [selectedFlagMapping, formik.values.sub_flags]);
+
+ const isSubFlagRequired = useMemo(() => {
+ return selectedFlagMapping?.allow_without_sub_flag === false;
+ }, [selectedFlagMapping]);
+
const addSupplierHandler = () => {
formik.setFieldValue('suppliers', [
...formik.values.suppliers,
@@ -213,7 +263,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const deleteSupplierItemHandler = (idx: number) => {
const path = 'suppliers';
- // trims values, errors, and touched at idx
removeArrayItemAndSync(formik, path, idx);
};
@@ -428,26 +477,48 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'}
/>
-
+
- (formik.values.flags || []).includes(opt.value)
+ value={productFlagMapping?.flags.find(
+ (opt) => opt.value === formik.values.flag
)}
+ onChange={(val) => {
+ const selectedFlag = String((val as OptionType)?.value ?? '');
+ formik.setFieldValue('flag', selectedFlag);
+ formik.setFieldValue('sub_flags', []);
+ }}
+ options={productFlagMapping?.flags ?? []}
+ isLoading={isLoadingConstants && !productFlagMapping}
+ isError={formik.touched.flag && Boolean(formik.errors.flag)}
+ errorMessage={formik.errors.flag as string}
+ isDisabled={type === 'detail'}
+ isClearable
+ />
+
+ {
const arr = Array.isArray(val) ? val : val ? [val] : [];
formik.setFieldValue(
- 'flags',
- arr.map((v) => (v as OptionType).value)
+ 'sub_flags',
+ arr.map((v) => String((v as OptionType).value))
);
}}
- options={PRODUCT_FLAG_OPTIONS}
- isError={formik.touched.flags && Boolean(formik.errors.flags)}
- errorMessage={formik.errors.flags as string}
- isDisabled={type === 'detail'}
+ options={subFlagOptions}
+ isLoading={isLoadingConstants && !productFlagMapping}
+ isError={
+ formik.touched.sub_flags && Boolean(formik.errors.sub_flags)
+ }
+ errorMessage={formik.errors.sub_flags as string}
+ isDisabled={type === 'detail' || !formik.values.flag}
+ closeMenuOnSelect={false}
isClearable
/>
diff --git a/src/components/pages/master-data/product/skeleton/ProductTableSkeleton.tsx b/src/components/pages/master-data/product/skeleton/ProductTableSkeleton.tsx
new file mode 100644
index 00000000..d0393421
--- /dev/null
+++ b/src/components/pages/master-data/product/skeleton/ProductTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { Product } from '@/types/api/master-data/product';
+import { ColumnDef } from '@tanstack/react-table';
+
+const ProductTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no product data displayed. Enter product data to get started.',
+}: {
+ columns: ColumnDef
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default ProductTableSkeleton;
diff --git a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx
index a8df6ae8..0ff8b594 100644
--- a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx
+++ b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx
@@ -1,92 +1,121 @@
'use client';
-import Button from '@/components/Button';
-import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
-import { ProductionStandard } from '@/types/api/master-data/production-standard';
-import { Icon } from '@iconify/react';
+import { useMemo, useState } from 'react';
import useSWR from 'swr';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
+import toast from 'react-hot-toast';
+
+import { Icon } from '@iconify/react';
+import Table from '@/components/Table';
+import Button from '@/components/Button';
+import { useModal } from '@/components/Modal';
+import ConfirmationModal from '@/components/modal/ConfirmationModal';
+import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import ProductionStandardTableSkeleton from '@/components/pages/master-data/production-standard/skeleton/ProductionStandardTableSkeleton';
+
+import { ProductionStandard } from '@/types/api/master-data/production-standard';
import { ProductionStandardApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
-import { CellContext } from '@tanstack/react-table';
-import { useModal } from '@/components/Modal';
-import { useState } from 'react';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import toast from 'react-hot-toast';
-import { cn } from '@/lib/helper';
-import RequirePermission from '@/components/helper/RequirePermission';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `production-standard#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-production-standard#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
const ProductionStandardTable = () => {
- const deleteModal = useModal();
+ const [sorting, setSorting] = useState([]);
+ const {
+ data: productionStandards,
+ isLoading,
+ mutate: refreshProductionStandards,
+ } = useSWR(
+ `${ProductionStandardApi.basePath}`,
+ ProductionStandardApi.getAllFetcher
+ );
+
+ const deleteModal = useModal();
const [selectedProductionStandard, setSelectedProductionStandard] = useState<
ProductionStandard | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const { data: productionStandards, mutate: refreshProductionStandards } =
- useSWR(
- `${ProductionStandardApi.basePath}`,
- ProductionStandardApi.getAllFetcher
- );
-
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -107,112 +136,120 @@ const ProductionStandardTable = () => {
setIsDeleteLoading(false);
};
+ const productionStandardColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) => props.row.index + 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorFn: (row) => row.project_category ?? '-',
+ header: 'Kategori',
+ },
+ {
+ 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;
+
+ const deleteClickHandler = () => {
+ setSelectedProductionStandard(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [deleteModal]
+ );
+
return (
<>
-
-
-
-
- Tambah
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Standard Production
+
+
+
-
-
- data={
- isResponseSuccess(productionStandards)
- ? productionStandards.data
- : []
- }
- columns={[
- {
- header: 'No',
- accessorFn: (row, index) => index + 1,
- },
- {
- header: 'Nama',
- accessorKey: 'name',
- },
- {
- header: 'Kategori',
- accessorFn: (row) => row.project_category,
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedProductionStandard(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ]}
- className={{
- headerColumnClassName: cn(
- TABLE_DEFAULT_STYLING.headerColumnClassName,
- 'last:flex last:flex-row last:justify-end'
- ),
- bodyColumnClassName: cn(
- TABLE_DEFAULT_STYLING.bodyColumnClassName,
- 'last:flex last:flex-row last:justify-end'
- ),
- }}
- />
-
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(productionStandards) ||
+ productionStandards.data?.length === 0 ? (
+
+ ) : (
+
+ data={productionStandards.data}
+ columns={productionStandardColumns}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
-
-
-
+
+
>
);
};
diff --git a/src/components/pages/master-data/production-standard/skeleton/ProductionStandardTableSkeleton.tsx b/src/components/pages/master-data/production-standard/skeleton/ProductionStandardTableSkeleton.tsx
new file mode 100644
index 00000000..590b4479
--- /dev/null
+++ b/src/components/pages/master-data/production-standard/skeleton/ProductionStandardTableSkeleton.tsx
@@ -0,0 +1,37 @@
+import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
+import Table from '@/components/Table';
+import { ProductionStandard } from '@/types/api/master-data/production-standard';
+import { ColumnDef } from '@tanstack/react-table';
+
+const ProductionStandardTableSkeleton = ({
+ columns,
+ icon,
+ title = 'No Data Available',
+ subtitle = 'There is no production standard data displayed. Enter production standard data to get started.',
+}: {
+ columns: ColumnDef[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default ProductionStandardTableSkeleton;
diff --git a/src/components/pages/master-data/supplier/SupplierTable.tsx b/src/components/pages/master-data/supplier/SupplierTable.tsx
index 2620c9e6..2b6cb227 100644
--- a/src/components/pages/master-data/supplier/SupplierTable.tsx
+++ b/src/components/pages/master-data/supplier/SupplierTable.tsx
@@ -1,87 +1,102 @@
'use client';
-import Button from '@/components/Button';
+import { ChangeEventHandler, useMemo, useState } from 'react';
+import useSWR from 'swr';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
+import toast from 'react-hot-toast';
+
+import { Icon } from '@iconify/react';
+import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
+import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import Table from '@/components/Table';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
-import { ROWS_OPTIONS } from '@/config/constant';
-import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { cn } from '@/lib/helper';
-import { SupplierApi } from '@/services/api/master-data';
-import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { Supplier } from '@/types/api/master-data/supplier';
-import { Icon } from '@iconify/react';
-import { CellContext, ColumnDef } from '@tanstack/react-table';
-import { useState } from 'react';
-import toast from 'react-hot-toast';
-import useSWR from 'swr';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import SupplierTableSkeleton from '@/components/pages/master-data/supplier/skeleton/SupplierTableSkeleton';
-const RowOptions = ({
- type = 'dropdown',
+import { Supplier } from '@/types/api/master-data/supplier';
+import { SupplierApi } from '@/services/api/master-data';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
+
+const RowOptionsMenu = ({
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `supplier#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-supplier#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
-
-
-
- Edit
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -93,15 +108,17 @@ const SuppliersTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '' },
+ initial: {
+ search: '',
+ },
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
},
});
- // Fetch Data
+ const [sorting, setSorting] = useState([]);
+
const {
data: suppliers,
isLoading,
@@ -111,97 +128,16 @@ const SuppliersTable = () => {
SupplierApi.getAllFetcher
);
- // State
const deleteModal = useModal();
const [selectedSupplier, setSelectedSupplier] = useState<
Supplier | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- // Columns Definition
- const suppliersColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'alias',
- header: 'Alias',
- },
- {
- accessorKey: 'pic',
- header: 'Nama PIC',
- },
- {
- accessorKey: 'category',
- header: 'Kategori',
- },
- {
- accessorKey: 'type',
- header: 'Tipe',
- },
- {
- accessorKey: 'phone',
- header: 'No. Telp',
- },
- {
- accessorKey: 'email',
- header: 'Email',
- },
- {
- accessorKey: 'address',
- header: 'Alamat',
- },
- {
- header: 'Aksi',
- 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 searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
- const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
-
- const deleteClickHandler = () => {
- setSelectedSupplier(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
-
- // Handler
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -221,82 +157,161 @@ const SuppliersTable = () => {
toast.success('Successfully delete Supplier!');
setIsDeleteLoading(false);
};
- const searchChangeHandler = (e: React.ChangeEvent) => {
- updateFilter('search', e.target.value);
- };
- const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
- setPageSize(newVal.value as number);
- };
+
+ const suppliersColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'alias',
+ header: 'Alias',
+ },
+ {
+ accessorKey: 'pic',
+ header: 'Nama PIC',
+ },
+ {
+ accessorKey: 'category',
+ header: 'Kategori',
+ },
+ {
+ accessorKey: 'type',
+ header: 'Tipe',
+ },
+ {
+ accessorKey: 'phone',
+ header: 'No. Telp',
+ },
+ {
+ accessorKey: 'email',
+ header: 'Email',
+ },
+ {
+ accessorKey: 'address',
+ header: 'Alamat',
+ },
+ {
+ 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;
+
+ const deleteClickHandler = () => {
+ setSelectedSupplier(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add Supplier
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(suppliers) ? suppliers?.data : []}
- columns={suppliersColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(suppliers) ? suppliers?.meta?.page : 0}
- totalItems={
- isResponseSuccess(suppliers) ? suppliers?.meta?.total_results : 0
- }
- onPageChange={setPage}
- isLoading={isLoading}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(suppliers) && suppliers?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(suppliers) || suppliers.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={suppliers?.data}
+ columns={suppliersColumns}
+ pageSize={tableFilterState.pageSize}
+ page={suppliers?.meta?.page ?? 0}
+ totalItems={suppliers?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default SupplierTableSkeleton;
diff --git a/src/components/pages/master-data/uom/UomsTable.tsx b/src/components/pages/master-data/uom/UomsTable.tsx
index 51e95661..aeaae276 100644
--- a/src/components/pages/master-data/uom/UomsTable.tsx
+++ b/src/components/pages/master-data/uom/UomsTable.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChangeEventHandler, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
@@ -11,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import UomTableSkeleton from '@/components/pages/master-data/uom/skeleton/UomTableSkeleton';
import { Uom } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `uom#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-uom#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -87,10 +108,17 @@ const UomsTable = () => {
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
- initial: { search: '', nameSort: '' },
- paramMap: { page: 'page', pageSize: 'limit', nameSort: 'sort_name' },
+ initial: {
+ search: '',
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ },
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: uoms,
isLoading,
@@ -101,65 +129,12 @@ const UomsTable = () => {
);
const deleteModal = useModal();
-
const [selectedUom, setSelectedUom] = useState(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const uomsColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedUom(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -179,93 +154,130 @@ const UomsTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const uomsColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedUom(props.row.original);
+ deleteModal.openModal();
+ };
- // track sorting
- useEffect(() => {
- const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
-
- if (!isNameSorted) {
- updateFilter('nameSort', '');
- } else {
- updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
- }
- }, [sorting, updateFilter]);
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
+ );
return (
<>
-
-
-
-
-
-
-
- Tambah
-
-
-
+
+ {/* Header Section */}
+
+ {/* Action Buttons */}
+
+
+
+
+ Add UOM
+
+
+
+ {/* Search */}
+
-
-
-
-
+ }
+ 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',
}}
- onChange={pageSizeChangeHandler}
- className={{ wrapper: 'max-w-28' }}
/>
-
- data={isResponseSuccess(uoms) ? uoms?.data : []}
- columns={uomsColumns}
- pageSize={tableFilterState.pageSize}
- page={isResponseSuccess(uoms) ? uoms?.meta?.page : 0}
- totalItems={isResponseSuccess(uoms) ? uoms?.meta?.total_results : 0}
- onPageChange={setPage}
- isLoading={isLoading}
- sorting={sorting}
- setSorting={setSorting}
- className={{
- containerClassName: cn({
- 'mb-20': isResponseSuccess(uoms) && uoms?.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',
- }}
- />
+ {/* Table Section */}
+
+ {isLoading ? (
+
+
+
+ ) : !isResponseSuccess(uoms) || uoms.data?.length === 0 ? (
+
+
+ }
+ />
+
+ ) : (
+
+ data={uoms?.data}
+ columns={uomsColumns}
+ pageSize={tableFilterState.pageSize}
+ page={uoms?.meta?.page ?? 0}
+ totalItems={uoms?.meta?.total_results ?? 0}
+ onPageChange={setPage}
+ onPageSizeChange={setPageSize}
+ isLoading={false}
+ sorting={sorting}
+ setSorting={setSorting}
+ className={{
+ containerClassName: 'p-3 mb-0',
+ headerColumnClassName: 'text-nowrap',
+ }}
+ />
+ )}
+
[];
+ icon: React.ReactNode;
+ title?: string;
+ subtitle?: string;
+}) => {
+ return (
+
+ );
+};
+
+export default UomTableSkeleton;
diff --git a/src/components/pages/master-data/warehouse/WarehousesTable.tsx b/src/components/pages/master-data/warehouse/WarehousesTable.tsx
index 62c39574..c800e8eb 100644
--- a/src/components/pages/master-data/warehouse/WarehousesTable.tsx
+++ b/src/components/pages/master-data/warehouse/WarehousesTable.tsx
@@ -1,13 +1,8 @@
'use client';
-import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
+import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr';
-import {
- CellContext,
- ColumnDef,
- ColumnSort,
- SortingState,
-} from '@tanstack/react-table';
+import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
@@ -16,71 +11,92 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import RowDropdownOptions from '@/components/table/RowDropdownOptions';
-import RowCollapseOptions from '@/components/table/RowCollapseOptions';
-import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
+import PopoverButton from '@/components/popover/PopoverButton';
+import PopoverContent from '@/components/popover/PopoverContent';
+import WarehouseTableSkeleton from '@/components/pages/master-data/warehouse/skeleton/WarehouseTableSkeleton';
import { Warehouse } from '@/types/api/master-data/warehouse';
import { WarehouseApi } from '@/services/api/master-data';
-import { cn } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { ROWS_OPTIONS } from '@/config/constant';
const RowOptionsMenu = ({
- type = 'dropdown',
+ popoverPosition = 'bottom',
props,
deleteClickHandler,
}: {
- type: 'dropdown' | 'collapse';
+ popoverPosition: 'bottom' | 'top';
props: CellContext;
deleteClickHandler: () => void;
}) => {
+ const popoverId = `warehouse#${props.row.original.id}`;
+ const popoverAnchorName = `--anchor-warehouse#${props.row.original.id}`;
+
+ const closePopover = () => {
+ document.getElementById(popoverId)?.hidePopover();
+ };
+
return (
-
-
-
-
- Detail
-
-
+
+
+
+
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
+
+
+
+
+
+ Detail
+
+
+
+
+
+ Edit
+
+
+
+ {
+ deleteClickHandler();
+ closePopover();
+ }}
+ variant='ghost'
+ color='none'
+ className='p-3 justify-start text-sm font-semibold w-full text-error hover:text-error'
+ >
+
+ Delete
+
+
+
+
+
);
};
@@ -94,23 +110,15 @@ const WarehousesTable = () => {
} = useTableFilter({
initial: {
search: '',
- nameSort: '',
- typeSort: '',
- areaSort: '',
- locationSort: '',
- kandangSort: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
- nameSort: 'sort_name',
- typeSort: 'sort_type',
- areaSort: ' sort_area',
- locationSort: ' sort_location',
- kandangSort: ' sort_kandang',
},
});
+ const [sorting, setSorting] = useState([]);
+
const {
data: warehouses,
isLoading,
@@ -121,101 +129,14 @@ const WarehousesTable = () => {
);
const deleteModal = useModal();
-
const [selectedWarehouse, setSelectedWarehouse] = useState<
Warehouse | undefined
>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [sorting, setSorting] = useState([]);
-
- const warehousesColumns: ColumnDef[] = [
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama',
- },
- {
- accessorKey: 'type',
- header: 'Tipe',
- },
- {
- accessorKey: 'area',
- header: 'Area',
- cell: (props) => props.row.original.area.name,
- },
- {
- accessorKey: 'location',
- header: 'Lokasi',
- cell: (props) => {
- if (
- props.row.original.type === 'LOKASI' ||
- props.row.original.type === 'KANDANG'
- ) {
- return props.row.original.location.name;
- } else {
- return '-';
- }
- },
- },
- {
- accessorKey: 'kandang',
- header: 'Kandang',
- cell: (props) => {
- if (props.row.original.type === 'KANDANG') {
- return props.row.original.kandang.name;
- } else {
- return '-';
- }
- },
- },
- {
- header: 'Aksi',
- 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 deleteClickHandler = () => {
- setSelectedWarehouse(props.row.original);
- deleteModal.openModal();
- };
-
- return (
- <>
- {currentPageSize > 2 && (
-
-
-
- )}
-
- {currentPageSize <= 2 && (
-
-
-
- )}
- >
- );
- },
- },
- ];
+ const searchChangeHandler: ChangeEventHandler = (e) => {
+ updateFilter('search', e.target.value);
+ };
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
@@ -237,118 +158,162 @@ const WarehousesTable = () => {
setIsDeleteLoading(false);
};
- const searchChangeHandler: ChangeEventHandler = (e) => {
- updateFilter('search', e.target.value);
- };
+ const warehousesColumns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'No',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'type',
+ header: 'Tipe',
+ },
+ {
+ accessorFn: (row) => row.area?.name ?? '-',
+ header: 'Area',
+ },
+ {
+ accessorKey: 'location',
+ header: 'Lokasi',
+ cell: (props) => {
+ if (
+ props.row.original.type === 'LOKASI' ||
+ props.row.original.type === 'KANDANG'
+ ) {
+ return props.row.original.location?.name ?? '-';
+ }
+ return '-';
+ },
+ },
+ {
+ accessorKey: 'kandang',
+ header: 'Kandang',
+ cell: (props) => {
+ if (props.row.original.type === 'KANDANG') {
+ return props.row.original.kandang?.name ?? '-';
+ }
+ return '-';
+ },
+ },
+ {
+ 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 pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
- const newVal = val as OptionType;
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
- setPageSize(newVal.value as number);
- };
+ const deleteClickHandler = () => {
+ setSelectedWarehouse(props.row.original);
+ deleteModal.openModal();
+ };
- const updateSortingFilter = useCallback(
- (
- sortName: Exclude,
- sortFilter: ColumnSort | undefined
- ) => {
- if (!sortFilter) {
- updateFilter(sortName, '');
- } else {
- updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc');
- }
- },
- [updateFilter]
+ return (
+
+ );
+ },
+ },
+ ],
+ [tableFilterState.pageSize, tableFilterState.page, deleteModal]
);
- // track sorting
- useEffect(() => {
- const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name');
- const typeSortFilter = sorting.find((sortItem) => sortItem.id === 'type');
- const areaSortFilter = sorting.find((sortItem) => sortItem.id === 'area');
- const locationSortFilter = sorting.find(
- (sortItem) => sortItem.id === 'location'
- );
- const kandangSortFilter = sorting.find(
- (sortItem) => sortItem.id === 'kandang'
- );
-
- updateSortingFilter('nameSort', nameSortFilter);
- updateSortingFilter('typeSort', typeSortFilter);
- updateSortingFilter('areaSort', areaSortFilter);
- updateSortingFilter('locationSort', locationSortFilter);
- updateSortingFilter('kandangSort', kandangSortFilter);
- }, [sorting, updateSortingFilter]);
-
return (
<>
-