diff --git a/src/components/pages/report/logistic-stock/LogisticStockTabs.tsx b/src/components/pages/report/logistic-stock/LogisticStockTabs.tsx index 1e2d2824..a7b844f3 100644 --- a/src/components/pages/report/logistic-stock/LogisticStockTabs.tsx +++ b/src/components/pages/report/logistic-stock/LogisticStockTabs.tsx @@ -1,14 +1,19 @@ 'use client'; +import { useState } from 'react'; import Tabs from '@/components/Tabs'; import PurchasesPerSupplierTab from '@/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab'; +import { useLogisticStockTabStore } from '@/stores/logistic-stock-tab/logistic-stock-tab.store'; const LogisticStockTabs = () => { + const [activeTabId, setActiveTabId] = useState('1'); + const tabActions = useLogisticStockTabStore((state) => state.tabActions); + const tabs = [ { id: '1', label: 'Rekapitulasi Pembelian Per Supplier', - content: , + content: , }, // { // id: '2', @@ -23,8 +28,20 @@ const LogisticStockTabs = () => { ]; return ( -
- +
+
); }; diff --git a/src/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton.tsx b/src/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton.tsx new file mode 100644 index 00000000..a5268b2f --- /dev/null +++ b/src/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton.tsx @@ -0,0 +1,37 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock'; +import { ColumnDef } from '@tanstack/react-table'; + +const PurchasePerSupplierSkeleton = ({ + columns, + icon, + title, + subtitle, +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title: string; + subtitle: string; +}) => { + return ( +
+ +
+ +
+ + ); +}; + +export default PurchasePerSupplierSkeleton; diff --git a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx index e1659470..4176e8ba 100644 --- a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx +++ b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx @@ -1,11 +1,9 @@ -import { useState, useMemo, useCallback } from 'react'; -import { ChangeEventHandler } from 'react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; import useSWR from 'swr'; import Card from '@/components/Card'; -import SelectInput, { - useSelect, - OptionType, -} from '@/components/input/SelectInput'; +import { useSelect, OptionType } from '@/components/input/SelectInput'; +import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; import DateInput from '@/components/input/DateInput'; import { AreaApi } from '@/services/api/master-data'; import { SupplierApi } from '@/services/api/master-data'; @@ -14,24 +12,30 @@ import { ProductCategoryApi } from '@/services/api/master-data'; import { LogisticApi } from '@/services/api/report/logistic-stock'; import Table from '@/components/Table'; import { ColumnDef } from '@tanstack/react-table'; -import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; +import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper'; import { LogisticPurchasePerSupplierReport, LogisticPurchasePerSupplierSummary, } from '@/types/api/report/logistic-stock'; import { isResponseSuccess } from '@/lib/api-helper'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; -import Pagination from '@/components/Pagination'; import Button from '@/components/Button'; import Dropdown from '@/components/Dropdown'; import MenuItem from '@/components/menu/MenuItem'; import Menu from '@/components/menu/Menu'; +import Modal from '@/components/Modal'; +import { useModal } from '@/components/Modal'; import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF'; import { generatePurchasesPerSupplierExcel } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportXLSX'; +import PurchasePerSupplierSkeleton from '@/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton'; import toast from 'react-hot-toast'; import { Icon } from '@iconify/react'; +import { useLogisticStockTabStore } from '@/stores/logistic-stock-tab/logistic-stock-tab.store'; -const PurchasesPerSupplierTab = () => { +interface PurchasesPerSupplierTabProps { + tabId: string; +} + +const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); @@ -39,31 +43,14 @@ const PurchasesPerSupplierTab = () => { // ===== PAGINATION STATE ===== const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(10); + const [pageSize] = useState(10); // ===== SUBMISSION STATE ===== const [isSubmitted, setIsSubmitted] = useState(false); - // ===== TABLE FILTER STATE ===== - const { state: tableFilterState, updateFilter } = useTableFilter({ - initial: { - area_id: [] as string[], - supplier_id: [] as string[], - product_id: [] as string[], - product_category_id: [] as string[], - received_date: '', - po_date: '', - start_date: '', - end_date: '', - sort_by: '', - filter_by: 'received_date', - }, - paramMap: { - page: 'page', - pageSize: 'limit', - }, - }); + const filterModal = useModal(); + // ===== OPTIONS (Declare before filter state) ===== const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect( AreaApi.basePath, 'id', @@ -100,117 +87,232 @@ const PurchasesPerSupplierTab = () => { [] ); - const areaChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'area_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] - ); + // ===== APPLIED FILTER STATE (Yang sudah di-apply) ===== + const [appliedFilterArea, setAppliedFilterArea] = useState< + typeof areaOptions + >([]); + const [appliedFilterSupplier, setAppliedFilterSupplier] = useState< + typeof supplierOptions + >([]); + const [appliedFilterProduct, setAppliedFilterProduct] = useState< + typeof productOptions + >([]); + const [appliedFilterProductCategory, setAppliedFilterProductCategory] = + useState([]); + const [appliedFilterByType, setAppliedFilterByType] = useState< + (typeof dataTypeOptions)[0] | null + >(null); + const [appliedFilterSortBy, setAppliedFilterSortBy] = useState< + (typeof sortByOptions)[0] | null + >(null); + const [appliedFilterStartDate, setAppliedFilterStartDate] = useState(''); + const [appliedFilterEndDate, setAppliedFilterEndDate] = useState(''); + const [dateErrorShown, setDateErrorShown] = useState(false); + const [hasDateError, setHasDateError] = useState(false); - const supplierChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'supplier_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] + // ===== PENDING FILTER STATE (Yang ada di modal, belum di-apply) ===== + const [filterArea, setFilterArea] = useState([]); + const [filterSupplier, setFilterSupplier] = useState( + [] ); + const [filterProduct, setFilterProduct] = useState([]); + const [filterProductCategory, setFilterProductCategory] = useState< + typeof productCategoryOptions + >([]); + const [filterByType, setFilterByType] = useState< + (typeof dataTypeOptions)[0] | null + >(null); + const [filterSortBy, setFilterSortBy] = useState< + (typeof sortByOptions)[0] | null + >(null); + const [filterStartDate, setFilterStartDate] = useState(''); + const [filterEndDate, setFilterEndDate] = useState(''); - const productChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'product_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] - ); + // ===== FILTER HANDLERS ===== + const handleFilterModalOpen = useCallback(() => { + setFilterArea(appliedFilterArea); + setFilterSupplier(appliedFilterSupplier); + setFilterProduct(appliedFilterProduct); + setFilterProductCategory(appliedFilterProductCategory); + setFilterByType(appliedFilterByType); + setFilterSortBy(appliedFilterSortBy); + setFilterStartDate(appliedFilterStartDate); + setFilterEndDate(appliedFilterEndDate); + filterModal.openModal(); + }, [ + filterModal, + appliedFilterArea, + appliedFilterSupplier, + appliedFilterProduct, + appliedFilterProductCategory, + appliedFilterByType, + appliedFilterSortBy, + appliedFilterStartDate, + appliedFilterEndDate, + ]); - const productCategoryChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'product_category_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const dataTypeChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - const filterValue = - (newVal?.value as 'received_date' | 'po_date') || 'received_date'; - updateFilter('filter_by', filterValue); - updateFilter('received_date', ''); - updateFilter('po_date', ''); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const sortByHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - const sortValue = (newVal?.value as 'ASC' | 'DESC') || 'ASC'; - updateFilter('sort_by', sortValue); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const startDateChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - updateFilter('start_date', val || ''); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const endDateChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - updateFilter('end_date', val || ''); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const resetFilters = useCallback(() => { - updateFilter('area_id', []); - updateFilter('supplier_id', []); - updateFilter('product_id', []); - updateFilter('product_category_id', []); - updateFilter('received_date', ''); - updateFilter('po_date', ''); - updateFilter('start_date', ''); - updateFilter('end_date', ''); - updateFilter('sort_by', ''); - updateFilter('filter_by', 'received_date'); + const handleResetFilters = useCallback(() => { setIsSubmitted(false); - }, [updateFilter]); + setFilterArea([]); + setFilterSupplier([]); + setFilterProduct([]); + setFilterProductCategory([]); + setFilterByType(null); + setFilterSortBy(null); + setFilterStartDate(''); + setFilterEndDate(''); + setAppliedFilterArea([]); + setAppliedFilterSupplier([]); + setAppliedFilterProduct([]); + setAppliedFilterProductCategory([]); + setAppliedFilterByType(null); + setAppliedFilterSortBy(null); + setAppliedFilterStartDate(''); + setAppliedFilterEndDate(''); + setHasDateError(false); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + }, [dateErrorShown]); - const handleSubmit = useCallback(() => { + const handleApplyFilters = useCallback(() => { + setAppliedFilterArea(filterArea); + setAppliedFilterSupplier(filterSupplier); + setAppliedFilterProduct(filterProduct); + setAppliedFilterProductCategory(filterProductCategory); + setAppliedFilterByType(filterByType); + setAppliedFilterSortBy(filterSortBy); + setAppliedFilterStartDate(filterStartDate); + setAppliedFilterEndDate(filterEndDate); setIsSubmitted(true); setCurrentPage(1); - }, []); + filterModal.closeModal(); + }, [ + filterModal, + filterArea, + filterSupplier, + filterProduct, + filterProductCategory, + filterByType, + filterSortBy, + filterStartDate, + filterEndDate, + ]); + + const handleStartDateChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setFilterStartDate(value); + + if (value && filterEndDate) { + const startDate = new Date(value); + const endDateObj = new Date(filterEndDate); + + if (endDateObj < startDate) { + setHasDateError(true); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + } else { + setHasDateError(false); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + } + } else { + setHasDateError(false); + } + }, + [filterEndDate, dateErrorShown] + ); + + const handleEndDateChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setFilterEndDate(value); + + if (value && filterStartDate) { + const startDateObj = new Date(filterStartDate); + const endDate = new Date(value); + + if (endDate < startDateObj) { + setHasDateError(true); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + return; + } + } + + setHasDateError(false); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + }, + [filterStartDate, dateErrorShown] + ); + + // ===== ACTIVE FILTERS COUNT ===== + const activeFiltersCount = useMemo(() => { + let count = 0; + + // Date filter (start_date + end_date = 1 filter) + if (appliedFilterStartDate || appliedFilterEndDate) { + count += 1; + } + + // Area filter + if (appliedFilterArea.length > 0) { + count += 1; + } + + // Supplier filter + if (appliedFilterSupplier.length > 0) { + count += 1; + } + + // Product filter + if (appliedFilterProduct.length > 0) { + count += 1; + } + + // Product category filter + if (appliedFilterProductCategory.length > 0) { + count += 1; + } + + // Filter by type filter + if (appliedFilterByType) { + count += 1; + } + + // Sort by filter + if (appliedFilterSortBy) { + count += 1; + } + + return count; + }, [ + appliedFilterStartDate, + appliedFilterEndDate, + appliedFilterArea, + appliedFilterSupplier, + appliedFilterProduct, + appliedFilterProductCategory, + appliedFilterByType, + appliedFilterSortBy, + ]); + + const hasFilters = activeFiltersCount > 0; // ===== DATA FETCHING ===== const { data: purchasePerSupplier, isLoading } = useSWR( @@ -218,33 +320,38 @@ const PurchasesPerSupplierTab = () => { ? () => { const params = { area_id: - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id.join(',') + appliedFilterArea.length > 0 + ? appliedFilterArea.map((v) => String(v.value)).join(',') : undefined, supplier_id: - tableFilterState.supplier_id.length > 0 - ? tableFilterState.supplier_id.join(',') + appliedFilterSupplier.length > 0 + ? appliedFilterSupplier.map((v) => String(v.value)).join(',') : undefined, product_id: - tableFilterState.product_id.length > 0 - ? tableFilterState.product_id.join(',') + appliedFilterProduct.length > 0 + ? appliedFilterProduct.map((v) => String(v.value)).join(',') : undefined, product_category_id: - tableFilterState.product_category_id.length > 0 - ? tableFilterState.product_category_id.join(',') + appliedFilterProductCategory.length > 0 + ? appliedFilterProductCategory + .map((v) => String(v.value)) + .join(',') : undefined, received_date: - tableFilterState.filter_by === 'received_date' - ? tableFilterState.start_date || undefined + appliedFilterByType?.value === 'received_date' + ? appliedFilterStartDate || undefined : undefined, po_date: - tableFilterState.filter_by === 'po_date' - ? tableFilterState.start_date || undefined + appliedFilterByType?.value === 'po_date' + ? appliedFilterStartDate || undefined : undefined, - start_date: tableFilterState.start_date || undefined, - end_date: tableFilterState.end_date || undefined, - sort_by: tableFilterState.sort_by || undefined, - filter_by: tableFilterState.filter_by || undefined, + start_date: appliedFilterStartDate || undefined, + end_date: appliedFilterEndDate || undefined, + sort_by: + (appliedFilterSortBy?.value as 'ASC' | 'DESC') || undefined, + filter_by: + (appliedFilterByType?.value as 'received_date' | 'po_date') || + undefined, page: currentPage, limit: pageSize, }; @@ -289,33 +396,35 @@ const PurchasesPerSupplierTab = () => { > => { const params = { area_id: - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id.join(',') + appliedFilterArea.length > 0 + ? appliedFilterArea.map((v) => String(v.value)).join(',') : undefined, supplier_id: - tableFilterState.supplier_id.length > 0 - ? tableFilterState.supplier_id.join(',') + appliedFilterSupplier.length > 0 + ? appliedFilterSupplier.map((v) => String(v.value)).join(',') : undefined, product_id: - tableFilterState.product_id.length > 0 - ? tableFilterState.product_id.join(',') + appliedFilterProduct.length > 0 + ? appliedFilterProduct.map((v) => String(v.value)).join(',') : undefined, product_category_id: - tableFilterState.product_category_id.length > 0 - ? tableFilterState.product_category_id.join(',') + appliedFilterProductCategory.length > 0 + ? appliedFilterProductCategory.map((v) => String(v.value)).join(',') : undefined, received_date: - tableFilterState.filter_by === 'received_date' - ? tableFilterState.start_date || undefined + appliedFilterByType?.value === 'received_date' + ? appliedFilterStartDate || undefined : undefined, po_date: - tableFilterState.filter_by === 'po_date' - ? tableFilterState.start_date || undefined + appliedFilterByType?.value === 'po_date' + ? appliedFilterStartDate || undefined : undefined, - start_date: tableFilterState.start_date || undefined, - end_date: tableFilterState.end_date || undefined, - sort_by: tableFilterState.sort_by || undefined, - filter_by: tableFilterState.filter_by || undefined, + start_date: appliedFilterStartDate || undefined, + end_date: appliedFilterEndDate || undefined, + sort_by: (appliedFilterSortBy?.value as 'ASC' | 'DESC') || undefined, + filter_by: + (appliedFilterByType?.value as 'received_date' | 'po_date') || + undefined, limit: 100, page: 1, }; @@ -338,7 +447,16 @@ const PurchasesPerSupplierTab = () => { return isResponseSuccess(response) ? (response.data as unknown as LogisticPurchasePerSupplierReport[]) : null; - }, [tableFilterState]); + }, [ + appliedFilterArea, + appliedFilterSupplier, + appliedFilterProduct, + appliedFilterProductCategory, + appliedFilterStartDate, + appliedFilterEndDate, + appliedFilterByType, + appliedFilterSortBy, + ]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { @@ -379,48 +497,26 @@ const PurchasesPerSupplierTab = () => { } const areaName = - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id - .map( - (id) => - areaOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Area' + appliedFilterArea.length > 0 + ? appliedFilterArea.map((c) => c.label).join(', ') || 'Semua Area' : 'Semua Area'; const supplierName = - tableFilterState.supplier_id.length > 0 - ? tableFilterState.supplier_id - .map( - (id) => - supplierOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Supplier' + appliedFilterSupplier.length > 0 + ? appliedFilterSupplier.map((c) => c.label).join(', ') || + 'Semua Supplier' : 'Semua Supplier'; const productName = - tableFilterState.product_id.length > 0 - ? tableFilterState.product_id - .map( - (id) => - productOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Produk' + appliedFilterProduct.length > 0 + ? appliedFilterProduct.map((c) => c.label).join(', ') || + 'Semua Produk' : 'Semua Produk'; const productCategoryName = - tableFilterState.product_category_id.length > 0 - ? tableFilterState.product_category_id - .map( - (id) => - productCategoryOptions.find((opt) => opt.value === Number(id)) - ?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Kategori Produk' + appliedFilterProductCategory.length > 0 + ? appliedFilterProductCategory.map((c) => c.label).join(', ') || + 'Semua Kategori Produk' : 'Semua Kategori Produk'; const exportParams = { @@ -428,9 +524,11 @@ const PurchasesPerSupplierTab = () => { supplier_name: supplierName, product_name: productName, product_category_name: productCategoryName, - filter_by: tableFilterState.filter_by || 'received_date', - start_date: tableFilterState.start_date || '', - end_date: tableFilterState.end_date || '', + filter_by: + (appliedFilterByType?.value as 'received_date' | 'po_date') || + undefined, + start_date: appliedFilterStartDate || undefined, + end_date: appliedFilterEndDate || undefined, }; await generatePurchasesPerSupplierPDF({ @@ -445,33 +543,101 @@ const PurchasesPerSupplierTab = () => { } }, [ logisticPurchasePerSupplierExport, - tableFilterState, - areaOptions, - supplierOptions, - productOptions, - productCategoryOptions, + appliedFilterArea, + appliedFilterSupplier, + appliedFilterProduct, + appliedFilterProductCategory, + appliedFilterStartDate, + appliedFilterEndDate, + appliedFilterByType, ]); - // ===== PAGINATION HANDLERS ===== - const handlePageChange = (page: number) => { - setCurrentPage(page); - }; + // ===== REGISTER TAB ACTIONS TO STORE ===== + const setTabActions = useLogisticStockTabStore( + (state) => state.setTabActions + ); + const clearTabActions = useLogisticStockTabStore( + (state) => state.clearTabActions + ); - const handleRowChange = (pageSize: number) => { - setPageSize(pageSize); - }; + useEffect(() => { + setTabActions( + tabId, +
+ - const handleNextPage = () => { - if (meta && currentPage < meta.total_pages) { - setCurrentPage(currentPage + 1); - } - }; + + + Export +
+ +
+ + } + align='end' + className={{ + content: + 'mt-1 p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg', + }} + > + + + + +
+
+ ); + }, [ + tabId, + hasFilters, + activeFiltersCount, + isAnyExportLoading, + filterModal.open, + setTabActions, + ]); - const handlePrevPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1); - } - }; + useEffect(() => { + return () => { + clearTabActions(tabId); + }; + }, [tabId, clearTabActions]); const getTableColumns = ( summary: LogisticPurchasePerSupplierSummary @@ -485,11 +651,11 @@ const PurchasesPerSupplierTab = () => { cell: (props) => props.row.index + 1, footer: () =>
Total
, }, - { id: 'received_date', header: 'Tanggal Terima', accessorKey: 'receive_date', + enableSorting: false, cell: (props) => { const value = props.row.original.receive_date; return formatDate(value, 'DD MMM YYYY'); @@ -499,6 +665,7 @@ const PurchasesPerSupplierTab = () => { id: 'po_date', header: 'Tanggal PO', accessorKey: 'po_date', + enableSorting: false, cell: (props) => { const value = props.row.original.po_date; return formatDate(value, 'DD MMM YYYY'); @@ -508,6 +675,7 @@ const PurchasesPerSupplierTab = () => { id: 'po_number', header: 'No. Referensi', accessorKey: 'po_number', + enableSorting: false, cell: (props) => { const value = props.row.original.po_number; return value || '-'; @@ -517,6 +685,7 @@ const PurchasesPerSupplierTab = () => { id: 'product_name', header: 'Nama Produk', accessorKey: 'product.name', + enableSorting: false, cell: (props) => { const product = props.row.original.product; return product?.name || '-'; @@ -526,6 +695,7 @@ const PurchasesPerSupplierTab = () => { id: 'destination_warehouse', header: 'Tujuan', accessorKey: 'warehouse.name', + enableSorting: false, cell: (props) => { const warehouse = props.row.original.warehouse; return warehouse?.name || '-'; @@ -535,6 +705,7 @@ const PurchasesPerSupplierTab = () => { id: 'qty', header: 'QTY', accessorKey: 'qty', + enableSorting: false, cell: (props) => { const value = props.row.original.qty; return
{formatNumber(value)}
; @@ -549,6 +720,7 @@ const PurchasesPerSupplierTab = () => { id: 'price', header: 'Harga Beli (Rp)', accessorKey: 'unit_price', + enableSorting: false, cell: (props) => { const value = props.row.original.unit_price; return
{formatCurrency(value)}
; @@ -563,6 +735,7 @@ const PurchasesPerSupplierTab = () => { id: 'purchase_amount', header: 'Value Harga Beli (Rp)', accessorKey: 'purchase_value', + enableSorting: false, cell: (props) => { const value = props.row.original.purchase_value; return
{formatCurrency(value)}
; @@ -577,6 +750,7 @@ const PurchasesPerSupplierTab = () => { id: 'transport', header: 'Transport (Rp)', accessorKey: 'transport_unit_price', + enableSorting: false, cell: (props) => { const value = props.row.original.transport_unit_price; return
{formatCurrency(value)}
; @@ -591,6 +765,7 @@ const PurchasesPerSupplierTab = () => { id: 'value_transport', header: 'Value Transport (Rp)', accessorKey: 'transport_value', + enableSorting: false, cell: (props) => { const value = props.row.original.transport_value; return
{formatCurrency(value)}
; @@ -605,6 +780,7 @@ const PurchasesPerSupplierTab = () => { id: 'total', header: 'Jumlah (Rp)', accessorKey: 'total_amount', + enableSorting: false, cell: (props) => { const value = props.row.original.total_amount; return
{formatCurrency(value)}
; @@ -619,6 +795,7 @@ const PurchasesPerSupplierTab = () => { id: 'expedition_vendor_name', header: 'Ekspedisi', accessorKey: 'expedition', + enableSorting: false, cell: (props) => { const value = props.row.original.expedition; return value || '-'; @@ -628,6 +805,7 @@ const PurchasesPerSupplierTab = () => { id: 'travel_number', header: 'Surat Jalan', accessorKey: 'delivery_number', + enableSorting: false, cell: (props) => { const value = props.row.original.delivery_number; return value || '-'; @@ -638,156 +816,50 @@ const PurchasesPerSupplierTab = () => { }; return ( -
- -
- - (tableFilterState.area_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={areaChangeHandler} - isLoading={isLoadingAreas} - isClearable - /> - - (tableFilterState.supplier_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={supplierChangeHandler} - isLoading={isLoadingSuppliers} - isClearable - /> - - (tableFilterState.product_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={productChangeHandler} - isLoading={isLoadingProducts} - isClearable - /> -
-
- - (tableFilterState.product_category_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={productCategoryChangeHandler} - isLoading={isLoadingProductCategories} - isClearable - /> -
- option.value === tableFilterState.filter_by - ) || null - } - onChange={dataTypeChangeHandler} - isLoading={false} - isClearable={false} - /> - option.value === tableFilterState.sort_by - ) || null - } - onChange={sortByHandler} - isLoading={false} - isClearable={false} - /> -
-
- - -
-
-
- - - - Export - - - } - align='end' - > - - - - - -
- + <> +
{!isSubmitted ? ( -
- Silakan pilih filter dan klik tombol Submit untuk menampilkan data. -
+ + } + title='No Filters Selected' + subtitle='Please choose filters to narrow down your results and make your search easier.' + /> ) : isLoading ? ( -
- -
+ + } + title='Memuat Data Pembelian Per Supplier' + subtitle='Silakan tunggu sebentar...' + /> ) : data.length === 0 ? ( -
- Tidak ada data yang dapat ditampilkan... -
+ + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> ) : ( data.map((supplierReport) => { const summary = supplierReport.summary || { @@ -808,15 +880,17 @@ const PurchasesPerSupplierTab = () => { title={supplierReport.supplier.name} subtitle={`Total Pembelian: ${formatCurrency(totalPurchase)}`} className={{ - wrapper: 'w-full rounded-2xl', + wrapper: 'w-full rounded-lg border-none', body: 'p-0', title: - 'py-1.5 px-3 bg-primary text-white text-lg font-normal', + 'px-2 py-1.5 font-normal text-sm bg-primary text-white', subtitle: - 'px-3 pb-1 bg-primary text-white text-sm font-normal', + 'px-2 pb-1.5 bg-primary text-white text-xs font-normal', + collapsible: 'rounded-lg', }} variant='bordered' collapsible={true} + defaultCollapsed={true} >
{ renderFooter={supplierReport.rows.length > 0} className={{ containerClassName: 'w-full mb-0!', - tableWrapperClassName: 'overflow-x-auto', + tableWrapperClassName: + 'overflow-x-auto rounded-tr-none rounded-tl-none', tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', headerColumnClassName: @@ -846,22 +921,161 @@ const PurchasesPerSupplierTab = () => { ); }) )} - - {meta && data.length > 0 && ( -
- + + {/* Filter Modal */} + + {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+ {/* Date Filter */} +
+ +
+ +
+ +
+
+ + {/* Area Filter */} + { + setFilterArea(Array.isArray(val) ? val : val ? [val] : []); + }} + isLoading={isLoadingAreas} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Supplier Filter */} + { + setFilterSupplier(Array.isArray(val) ? val : val ? [val] : []); + }} + isLoading={isLoadingSuppliers} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Product Filter */} + { + setFilterProduct(Array.isArray(val) ? val : val ? [val] : []); + }} + isLoading={isLoadingProducts} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Product Category Filter */} + { + setFilterProductCategory( + Array.isArray(val) ? val : val ? [val] : [] + ); + }} + isLoading={isLoadingProductCategories} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Filter By Type */} + { + if (val && !Array.isArray(val)) { + setFilterByType(val); + } + }} + className={{ wrapper: 'w-full' }} + /> + + {/* Sort By */} + { + if (val && !Array.isArray(val)) { + setFilterSortBy(val); + } + }} + className={{ wrapper: 'w-full' }} />
- )} -
+ + {/* Modal Footer */} +
+ + +
+ + ); }; diff --git a/src/stores/logistic-stock-tab/logistic-stock-tab.store.ts b/src/stores/logistic-stock-tab/logistic-stock-tab.store.ts new file mode 100644 index 00000000..f9e142b1 --- /dev/null +++ b/src/stores/logistic-stock-tab/logistic-stock-tab.store.ts @@ -0,0 +1,51 @@ +'use client'; + +import { ReactNode } from 'react'; +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +export type LogisticStockTabActionsSlice = { + // State - actions per tab ID + tabActions: Record; + + // Actions + setTabActions: (tabId: string, actions: ReactNode) => void; + clearTabActions: (tabId: string) => void; + clearAllTabActions: () => void; +}; + +export const useLogisticStockTabStore = create()( + devtools( + (set) => ({ + tabActions: {}, + + setTabActions: (tabId, actions) => + set( + (state) => ({ + tabActions: { + ...state.tabActions, + [tabId]: actions, + }, + }), + false, + 'setTabActions' + ), + + clearTabActions: (tabId) => + set( + (state) => { + const { [tabId]: _, ...rest } = state.tabActions; + return { tabActions: rest }; + }, + false, + 'clearTabActions' + ), + + clearAllTabActions: () => + set({ tabActions: {} }, false, 'clearAllTabActions'), + }), + { + name: 'LogisticStockTabStore', + } + ) +);