mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
refactor(FE): Refactor PurchasesPerSupplierTab to use Formik for filters
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
export type PurchasesPerSupplierFilterType = {
|
||||||
|
start_date: string | null | undefined;
|
||||||
|
end_date: string | null | undefined;
|
||||||
|
area_ids: OptionType[] | null | undefined;
|
||||||
|
supplier_ids: OptionType[] | null | undefined;
|
||||||
|
product_ids: OptionType[] | null | undefined;
|
||||||
|
product_category_ids: OptionType[] | null | undefined;
|
||||||
|
filter_by: OptionType | null | undefined;
|
||||||
|
sort_by: OptionType | null | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PurchasesPerSupplierFilterSchema: yup.ObjectSchema<PurchasesPerSupplierFilterType> =
|
||||||
|
yup.object({
|
||||||
|
start_date: yup.string().optional().nullable(),
|
||||||
|
end_date: yup
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-greater-than-start',
|
||||||
|
'Tanggal akhir tidak boleh masa lampau',
|
||||||
|
function (value) {
|
||||||
|
const { start_date } = this.parent;
|
||||||
|
if (!start_date || !value) return true;
|
||||||
|
return new Date(value) >= new Date(start_date);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
area_ids: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
supplier_ids: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
product_ids: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
product_category_ids: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
filter_by: yup
|
||||||
|
.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
sort_by: yup
|
||||||
|
.object({
|
||||||
|
value: yup.mixed<string | number>().required(),
|
||||||
|
label: yup.string().required(),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PurchasesPerSupplierFilterValues = yup.InferType<
|
||||||
|
typeof PurchasesPerSupplierFilterSchema
|
||||||
|
>;
|
||||||
@@ -1,40 +1,55 @@
|
|||||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
import Button from '@/components/Button';
|
||||||
import useSWR from 'swr';
|
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import { useSelect, OptionType } from '@/components/input/SelectInput';
|
import Dropdown from '@/components/Dropdown';
|
||||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
|
||||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
|
import Menu from '@/components/menu/Menu';
|
||||||
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
import { AreaApi } from '@/services/api/master-data';
|
import { AreaApi } from '@/services/api/master-data';
|
||||||
import { SupplierApi } from '@/services/api/master-data';
|
import { SupplierApi } from '@/services/api/master-data';
|
||||||
import { ProductApi } from '@/services/api/master-data';
|
import { ProductApi } from '@/services/api/master-data';
|
||||||
import { ProductCategoryApi } from '@/services/api/master-data';
|
import { ProductCategoryApi } from '@/services/api/master-data';
|
||||||
import { LogisticApi } from '@/services/api/report/logistic-stock';
|
import { LogisticApi } from '@/services/api/report/logistic-stock';
|
||||||
import Table from '@/components/Table';
|
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
|
||||||
import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper';
|
|
||||||
import {
|
import {
|
||||||
LogisticPurchasePerSupplierReport,
|
LogisticPurchasePerSupplierReport,
|
||||||
LogisticPurchasePerSupplierSummary,
|
LogisticPurchasePerSupplierSummary,
|
||||||
} from '@/types/api/report/logistic-stock';
|
} from '@/types/api/report/logistic-stock';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
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 { generatePurchasesPerSupplierExcel } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportXLSX';
|
||||||
import PurchasePerSupplierSkeleton from '@/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton';
|
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import {
|
||||||
|
PurchasesPerSupplierFilterSchema,
|
||||||
|
PurchasesPerSupplierFilterType,
|
||||||
|
} from '@/components/pages/report/logistic-stock/filter/PurchasesPerSupplierFilter';
|
||||||
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||||
import { useLogisticStockTabStore } from '@/stores/logistic-stock-tab/logistic-stock-tab.store';
|
import { useLogisticStockTabStore } from '@/stores/logistic-stock-tab/logistic-stock-tab.store';
|
||||||
|
import PurchasePerSupplierSkeleton from '@/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton';
|
||||||
|
|
||||||
interface PurchasesPerSupplierTabProps {
|
interface PurchasesPerSupplierTabProps {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FilterParams {
|
||||||
|
area_ids?: string;
|
||||||
|
supplier_ids?: string;
|
||||||
|
product_ids?: string;
|
||||||
|
product_category_ids?: string;
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
sort_by?: string;
|
||||||
|
filter_by?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
||||||
@@ -46,11 +61,14 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
const [pageSize] = useState(10);
|
const [pageSize] = useState(10);
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
// ===== SUBMISSION STATE =====
|
||||||
|
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
// ===== OPTIONS (Declare before filter state) =====
|
// ===== OPTIONS =====
|
||||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
||||||
AreaApi.basePath,
|
AreaApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
@@ -87,127 +105,66 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== APPLIED FILTER STATE (Yang sudah di-apply) =====
|
const handleFilterModalOpen = () => {
|
||||||
const [appliedFilterArea, setAppliedFilterArea] = useState<
|
|
||||||
typeof areaOptions
|
|
||||||
>([]);
|
|
||||||
const [appliedFilterSupplier, setAppliedFilterSupplier] = useState<
|
|
||||||
typeof supplierOptions
|
|
||||||
>([]);
|
|
||||||
const [appliedFilterProduct, setAppliedFilterProduct] = useState<
|
|
||||||
typeof productOptions
|
|
||||||
>([]);
|
|
||||||
const [appliedFilterProductCategory, setAppliedFilterProductCategory] =
|
|
||||||
useState<typeof productCategoryOptions>([]);
|
|
||||||
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);
|
|
||||||
|
|
||||||
// ===== PENDING FILTER STATE (Yang ada di modal, belum di-apply) =====
|
|
||||||
const [filterArea, setFilterArea] = useState<typeof areaOptions>([]);
|
|
||||||
const [filterSupplier, setFilterSupplier] = useState<typeof supplierOptions>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [filterProduct, setFilterProduct] = useState<typeof productOptions>([]);
|
|
||||||
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('');
|
|
||||||
|
|
||||||
// ===== FILTER HANDLERS =====
|
|
||||||
const handleFilterModalOpen = useCallback(() => {
|
|
||||||
setFilterArea(appliedFilterArea);
|
|
||||||
setFilterSupplier(appliedFilterSupplier);
|
|
||||||
setFilterProduct(appliedFilterProduct);
|
|
||||||
setFilterProductCategory(appliedFilterProductCategory);
|
|
||||||
setFilterByType(appliedFilterByType);
|
|
||||||
setFilterSortBy(appliedFilterSortBy);
|
|
||||||
setFilterStartDate(appliedFilterStartDate);
|
|
||||||
setFilterEndDate(appliedFilterEndDate);
|
|
||||||
filterModal.openModal();
|
filterModal.openModal();
|
||||||
}, [
|
};
|
||||||
filterModal,
|
|
||||||
appliedFilterArea,
|
|
||||||
appliedFilterSupplier,
|
|
||||||
appliedFilterProduct,
|
|
||||||
appliedFilterProductCategory,
|
|
||||||
appliedFilterByType,
|
|
||||||
appliedFilterSortBy,
|
|
||||||
appliedFilterStartDate,
|
|
||||||
appliedFilterEndDate,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleResetFilters = useCallback(() => {
|
// ===== FORMIK SETUP =====
|
||||||
setIsSubmitted(false);
|
const formik = useFormik<PurchasesPerSupplierFilterType>({
|
||||||
setFilterArea([]);
|
initialValues: {
|
||||||
setFilterSupplier([]);
|
start_date: null,
|
||||||
setFilterProduct([]);
|
end_date: null,
|
||||||
setFilterProductCategory([]);
|
area_ids: null,
|
||||||
setFilterByType(null);
|
supplier_ids: null,
|
||||||
setFilterSortBy(null);
|
product_ids: null,
|
||||||
setFilterStartDate('');
|
product_category_ids: null,
|
||||||
setFilterEndDate('');
|
filter_by: null,
|
||||||
setAppliedFilterArea([]);
|
sort_by: null,
|
||||||
setAppliedFilterSupplier([]);
|
},
|
||||||
setAppliedFilterProduct([]);
|
validationSchema: PurchasesPerSupplierFilterSchema,
|
||||||
setAppliedFilterProductCategory([]);
|
onSubmit: (values) => {
|
||||||
setAppliedFilterByType(null);
|
setFilterParams({
|
||||||
setAppliedFilterSortBy(null);
|
start_date: values.start_date?.toString() || undefined,
|
||||||
setAppliedFilterStartDate('');
|
end_date: values.end_date?.toString() || undefined,
|
||||||
setAppliedFilterEndDate('');
|
area_ids:
|
||||||
setHasDateError(false);
|
values.area_ids?.map((v) => String(v.value)).join(',') || undefined,
|
||||||
if (dateErrorShown) {
|
supplier_ids:
|
||||||
toast.dismiss();
|
values.supplier_ids?.map((v) => String(v.value)).join(',') ||
|
||||||
setDateErrorShown(false);
|
undefined,
|
||||||
}
|
product_ids:
|
||||||
}, [dateErrorShown]);
|
values.product_ids?.map((v) => String(v.value)).join(',') ||
|
||||||
|
undefined,
|
||||||
const handleApplyFilters = useCallback(() => {
|
product_category_ids:
|
||||||
setAppliedFilterArea(filterArea);
|
values.product_category_ids?.map((v) => String(v.value)).join(',') ||
|
||||||
setAppliedFilterSupplier(filterSupplier);
|
undefined,
|
||||||
setAppliedFilterProduct(filterProduct);
|
filter_by: values.filter_by?.value?.toString() || undefined,
|
||||||
setAppliedFilterProductCategory(filterProductCategory);
|
sort_by: values.sort_by?.value?.toString() || undefined,
|
||||||
setAppliedFilterByType(filterByType);
|
});
|
||||||
setAppliedFilterSortBy(filterSortBy);
|
filterModal.closeModal();
|
||||||
setAppliedFilterStartDate(filterStartDate);
|
setIsSubmitted(true);
|
||||||
setAppliedFilterEndDate(filterEndDate);
|
setCurrentPage(1);
|
||||||
setIsSubmitted(true);
|
},
|
||||||
setCurrentPage(1);
|
onReset: () => {
|
||||||
filterModal.closeModal();
|
setFilterParams({});
|
||||||
}, [
|
setIsSubmitted(false);
|
||||||
filterModal,
|
setCurrentPage(1);
|
||||||
filterArea,
|
setHasDateError(false);
|
||||||
filterSupplier,
|
if (dateErrorShown) {
|
||||||
filterProduct,
|
toast.dismiss();
|
||||||
filterProductCategory,
|
setDateErrorShown(false);
|
||||||
filterByType,
|
}
|
||||||
filterSortBy,
|
},
|
||||||
filterStartDate,
|
});
|
||||||
filterEndDate,
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
// ===== DATE CHANGE HANDLERS =====
|
||||||
const handleStartDateChange = useCallback(
|
const handleStartDateChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setFilterStartDate(value);
|
formik.setFieldValue('start_date', value || null);
|
||||||
|
|
||||||
if (value && filterEndDate) {
|
if (value && formik.values.end_date) {
|
||||||
const startDate = new Date(value);
|
const startDate = new Date(value);
|
||||||
const endDateObj = new Date(filterEndDate);
|
const endDateObj = new Date(formik.values.end_date);
|
||||||
|
|
||||||
if (endDateObj < startDate) {
|
if (endDateObj < startDate) {
|
||||||
setHasDateError(true);
|
setHasDateError(true);
|
||||||
@@ -228,16 +185,16 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
setHasDateError(false);
|
setHasDateError(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[filterEndDate, dateErrorShown]
|
[formik, dateErrorShown]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEndDateChange = useCallback(
|
const handleEndDateChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setFilterEndDate(value);
|
formik.setFieldValue('end_date', value || null);
|
||||||
|
|
||||||
if (value && filterStartDate) {
|
if (value && formik.values.start_date) {
|
||||||
const startDateObj = new Date(filterStartDate);
|
const startDateObj = new Date(formik.values.start_date);
|
||||||
const endDate = new Date(value);
|
const endDate = new Date(value);
|
||||||
|
|
||||||
if (endDate < startDateObj) {
|
if (endDate < startDateObj) {
|
||||||
@@ -258,59 +215,43 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
setDateErrorShown(false);
|
setDateErrorShown(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[filterStartDate, dateErrorShown]
|
[formik, dateErrorShown]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== ACTIVE FILTERS COUNT =====
|
// ===== ACTIVE FILTERS COUNT =====
|
||||||
const activeFiltersCount = useMemo(() => {
|
const activeFiltersCount = useMemo(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
// Date filter (start_date + end_date = 1 filter)
|
if (filterParams.start_date || filterParams.end_date) {
|
||||||
if (appliedFilterStartDate || appliedFilterEndDate) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Area filter
|
if (filterParams.area_ids) {
|
||||||
if (appliedFilterArea.length > 0) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supplier filter
|
if (filterParams.supplier_ids) {
|
||||||
if (appliedFilterSupplier.length > 0) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Product filter
|
if (filterParams.product_ids) {
|
||||||
if (appliedFilterProduct.length > 0) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Product category filter
|
if (filterParams.product_category_ids) {
|
||||||
if (appliedFilterProductCategory.length > 0) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by type filter
|
if (filterParams.filter_by) {
|
||||||
if (appliedFilterByType) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by filter
|
if (filterParams.sort_by) {
|
||||||
if (appliedFilterSortBy) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}, [
|
}, [filterParams]);
|
||||||
appliedFilterStartDate,
|
|
||||||
appliedFilterEndDate,
|
|
||||||
appliedFilterArea,
|
|
||||||
appliedFilterSupplier,
|
|
||||||
appliedFilterProduct,
|
|
||||||
appliedFilterProductCategory,
|
|
||||||
appliedFilterByType,
|
|
||||||
appliedFilterSortBy,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const hasFilters = activeFiltersCount > 0;
|
const hasFilters = activeFiltersCount > 0;
|
||||||
|
|
||||||
@@ -319,39 +260,14 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
isSubmitted
|
isSubmitted
|
||||||
? () => {
|
? () => {
|
||||||
const params = {
|
const params = {
|
||||||
area_id:
|
area_ids: filterParams.area_ids,
|
||||||
appliedFilterArea.length > 0
|
supplier_ids: filterParams.supplier_ids,
|
||||||
? appliedFilterArea.map((v) => String(v.value)).join(',')
|
product_ids: filterParams.product_ids,
|
||||||
: undefined,
|
product_category_ids: filterParams.product_category_ids,
|
||||||
supplier_id:
|
start_date: filterParams.start_date,
|
||||||
appliedFilterSupplier.length > 0
|
end_date: filterParams.end_date,
|
||||||
? appliedFilterSupplier.map((v) => String(v.value)).join(',')
|
sort_by: filterParams.sort_by,
|
||||||
: undefined,
|
filter_by: filterParams.filter_by,
|
||||||
product_id:
|
|
||||||
appliedFilterProduct.length > 0
|
|
||||||
? appliedFilterProduct.map((v) => String(v.value)).join(',')
|
|
||||||
: undefined,
|
|
||||||
product_category_id:
|
|
||||||
appliedFilterProductCategory.length > 0
|
|
||||||
? appliedFilterProductCategory
|
|
||||||
.map((v) => String(v.value))
|
|
||||||
.join(',')
|
|
||||||
: undefined,
|
|
||||||
received_date:
|
|
||||||
appliedFilterByType?.value === 'received_date'
|
|
||||||
? appliedFilterStartDate || undefined
|
|
||||||
: undefined,
|
|
||||||
po_date:
|
|
||||||
appliedFilterByType?.value === 'po_date'
|
|
||||||
? appliedFilterStartDate || undefined
|
|
||||||
: 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,
|
page: currentPage,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
};
|
};
|
||||||
@@ -361,12 +277,12 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
: null,
|
: null,
|
||||||
([, params]) =>
|
([, params]) =>
|
||||||
LogisticApi.getLogisticPurchasePerSupplierReport(
|
LogisticApi.getLogisticPurchasePerSupplierReport(
|
||||||
params.area_id,
|
params.area_ids,
|
||||||
params.supplier_id,
|
params.supplier_ids,
|
||||||
params.product_id,
|
params.product_ids,
|
||||||
params.product_category_id,
|
params.product_category_ids,
|
||||||
params.received_date,
|
params.filter_by === 'received_date' ? params.start_date : undefined,
|
||||||
params.po_date,
|
params.filter_by === 'po_date' ? params.start_date : undefined,
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
params.sort_by,
|
params.sort_by,
|
||||||
@@ -395,47 +311,25 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
LogisticPurchasePerSupplierReport[] | null
|
LogisticPurchasePerSupplierReport[] | null
|
||||||
> => {
|
> => {
|
||||||
const params = {
|
const params = {
|
||||||
area_id:
|
area_ids: filterParams.area_ids,
|
||||||
appliedFilterArea.length > 0
|
supplier_ids: filterParams.supplier_ids,
|
||||||
? appliedFilterArea.map((v) => String(v.value)).join(',')
|
product_ids: filterParams.product_ids,
|
||||||
: undefined,
|
product_category_ids: filterParams.product_category_ids,
|
||||||
supplier_id:
|
start_date: filterParams.start_date,
|
||||||
appliedFilterSupplier.length > 0
|
end_date: filterParams.end_date,
|
||||||
? appliedFilterSupplier.map((v) => String(v.value)).join(',')
|
sort_by: filterParams.sort_by,
|
||||||
: undefined,
|
filter_by: filterParams.filter_by,
|
||||||
product_id:
|
|
||||||
appliedFilterProduct.length > 0
|
|
||||||
? appliedFilterProduct.map((v) => String(v.value)).join(',')
|
|
||||||
: undefined,
|
|
||||||
product_category_id:
|
|
||||||
appliedFilterProductCategory.length > 0
|
|
||||||
? appliedFilterProductCategory.map((v) => String(v.value)).join(',')
|
|
||||||
: undefined,
|
|
||||||
received_date:
|
|
||||||
appliedFilterByType?.value === 'received_date'
|
|
||||||
? appliedFilterStartDate || undefined
|
|
||||||
: undefined,
|
|
||||||
po_date:
|
|
||||||
appliedFilterByType?.value === 'po_date'
|
|
||||||
? appliedFilterStartDate || undefined
|
|
||||||
: 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,
|
limit: 100,
|
||||||
page: 1,
|
page: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await LogisticApi.getLogisticPurchasePerSupplierReport(
|
const response = await LogisticApi.getLogisticPurchasePerSupplierReport(
|
||||||
params.area_id,
|
params.area_ids,
|
||||||
params.supplier_id,
|
params.supplier_ids,
|
||||||
params.product_id,
|
params.product_ids,
|
||||||
params.product_category_id,
|
params.product_category_ids,
|
||||||
params.received_date,
|
params.filter_by === 'received_date' ? params.start_date : undefined,
|
||||||
params.po_date,
|
params.filter_by === 'po_date' ? params.start_date : undefined,
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
params.sort_by,
|
params.sort_by,
|
||||||
@@ -447,16 +341,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
return isResponseSuccess(response)
|
return isResponseSuccess(response)
|
||||||
? (response.data as unknown as LogisticPurchasePerSupplierReport[])
|
? (response.data as unknown as LogisticPurchasePerSupplierReport[])
|
||||||
: null;
|
: null;
|
||||||
}, [
|
}, [filterParams]);
|
||||||
appliedFilterArea,
|
|
||||||
appliedFilterSupplier,
|
|
||||||
appliedFilterProduct,
|
|
||||||
appliedFilterProductCategory,
|
|
||||||
appliedFilterStartDate,
|
|
||||||
appliedFilterEndDate,
|
|
||||||
appliedFilterByType,
|
|
||||||
appliedFilterSortBy,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ===== EXPORT HANDLERS =====
|
// ===== EXPORT HANDLERS =====
|
||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
@@ -496,39 +381,52 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const areaName =
|
const areaName = filterParams.area_ids
|
||||||
appliedFilterArea.length > 0
|
? areaOptions
|
||||||
? appliedFilterArea.map((c) => c.label).join(', ') || 'Semua Area'
|
.filter((opt) =>
|
||||||
: 'Semua Area';
|
filterParams.area_ids?.split(',').includes(String(opt.value))
|
||||||
|
)
|
||||||
|
.map((opt) => opt.label)
|
||||||
|
.join(', ') || 'Semua Area'
|
||||||
|
: 'Semua Area';
|
||||||
|
|
||||||
const supplierName =
|
const supplierName = filterParams.supplier_ids
|
||||||
appliedFilterSupplier.length > 0
|
? supplierOptions
|
||||||
? appliedFilterSupplier.map((c) => c.label).join(', ') ||
|
.filter((opt) =>
|
||||||
'Semua Supplier'
|
filterParams.supplier_ids?.split(',').includes(String(opt.value))
|
||||||
: 'Semua Supplier';
|
)
|
||||||
|
.map((opt) => opt.label)
|
||||||
|
.join(', ') || 'Semua Supplier'
|
||||||
|
: 'Semua Supplier';
|
||||||
|
|
||||||
const productName =
|
const productName = filterParams.product_ids
|
||||||
appliedFilterProduct.length > 0
|
? productOptions
|
||||||
? appliedFilterProduct.map((c) => c.label).join(', ') ||
|
.filter((opt) =>
|
||||||
'Semua Produk'
|
filterParams.product_ids?.split(',').includes(String(opt.value))
|
||||||
: 'Semua Produk';
|
)
|
||||||
|
.map((opt) => opt.label)
|
||||||
|
.join(', ') || 'Semua Produk'
|
||||||
|
: 'Semua Produk';
|
||||||
|
|
||||||
const productCategoryName =
|
const productCategoryName = filterParams.product_category_ids
|
||||||
appliedFilterProductCategory.length > 0
|
? productCategoryOptions
|
||||||
? appliedFilterProductCategory.map((c) => c.label).join(', ') ||
|
.filter((opt) =>
|
||||||
'Semua Kategori Produk'
|
filterParams.product_category_ids
|
||||||
: 'Semua Kategori Produk';
|
?.split(',')
|
||||||
|
.includes(String(opt.value))
|
||||||
|
)
|
||||||
|
.map((opt) => opt.label)
|
||||||
|
.join(', ') || 'Semua Kategori Produk'
|
||||||
|
: 'Semua Kategori Produk';
|
||||||
|
|
||||||
const exportParams = {
|
const exportParams = {
|
||||||
area_name: areaName,
|
area_name: areaName,
|
||||||
supplier_name: supplierName,
|
supplier_name: supplierName,
|
||||||
product_name: productName,
|
product_name: productName,
|
||||||
product_category_name: productCategoryName,
|
product_category_name: productCategoryName,
|
||||||
filter_by:
|
filter_by: filterParams.filter_by,
|
||||||
(appliedFilterByType?.value as 'received_date' | 'po_date') ||
|
start_date: filterParams.start_date,
|
||||||
undefined,
|
end_date: filterParams.end_date,
|
||||||
start_date: appliedFilterStartDate || undefined,
|
|
||||||
end_date: appliedFilterEndDate || undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await generatePurchasesPerSupplierPDF({
|
await generatePurchasesPerSupplierPDF({
|
||||||
@@ -543,13 +441,11 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
logisticPurchasePerSupplierExport,
|
logisticPurchasePerSupplierExport,
|
||||||
appliedFilterArea,
|
filterParams,
|
||||||
appliedFilterSupplier,
|
areaOptions,
|
||||||
appliedFilterProduct,
|
supplierOptions,
|
||||||
appliedFilterProductCategory,
|
productOptions,
|
||||||
appliedFilterStartDate,
|
productCategoryOptions,
|
||||||
appliedFilterEndDate,
|
|
||||||
appliedFilterByType,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||||
@@ -624,6 +520,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
tabId,
|
tabId,
|
||||||
hasFilters,
|
hasFilters,
|
||||||
@@ -633,6 +530,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
setTabActions,
|
setTabActions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
clearTabActions(tabId);
|
clearTabActions(tabId);
|
||||||
@@ -945,137 +843,184 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='p-4 flex flex-col gap-3'>
|
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
|
||||||
{/* Date Filter */}
|
<div className='p-4 flex flex-col gap-3'>
|
||||||
<div>
|
{/* Date Filter */}
|
||||||
<label className='block text-xs font-semibold text-base-content py-2'>
|
<div>
|
||||||
Tanggal
|
<label className='block text-xs font-semibold text-base-content py-2'>
|
||||||
</label>
|
Tanggal
|
||||||
<div className='flex flex-row gap-1.5 items-center justify-between'>
|
</label>
|
||||||
<DateInput
|
<div className='flex flex-row gap-1.5 items-center justify-between'>
|
||||||
name='start_date'
|
<DateInput
|
||||||
value={filterStartDate}
|
name='start_date'
|
||||||
onChange={handleStartDateChange}
|
value={formik.values.start_date || ''}
|
||||||
className={{ wrapper: 'w-full' }}
|
onChange={handleStartDateChange}
|
||||||
isNestedModal
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
isNestedModal
|
||||||
<hr className='w-full max-w-3 h-px border-base-content/10' />
|
/>
|
||||||
<DateInput
|
<hr className='w-full max-w-3 h-px border-base-content/10' />
|
||||||
name='end_date'
|
<DateInput
|
||||||
value={filterEndDate}
|
name='end_date'
|
||||||
onChange={handleEndDateChange}
|
value={formik.values.end_date || ''}
|
||||||
className={{ wrapper: 'w-full' }}
|
onChange={handleEndDateChange}
|
||||||
isNestedModal
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
isNestedModal
|
||||||
|
isError={hasDateError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Area Filter */}
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Area'
|
||||||
|
placeholder='Pilih Area'
|
||||||
|
options={areaOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.area_ids as
|
||||||
|
| { value: number; label: string }
|
||||||
|
| { value: number; label: string }[]
|
||||||
|
| null
|
||||||
|
| undefined) || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'area_ids',
|
||||||
|
Array.isArray(val) ? val : val ? [val] : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingAreas}
|
||||||
|
isClearable
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Supplier Filter */}
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Supplier'
|
||||||
|
placeholder='Pilih Supplier'
|
||||||
|
options={supplierOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.supplier_ids as
|
||||||
|
| { value: number; label: string }
|
||||||
|
| { value: number; label: string }[]
|
||||||
|
| null
|
||||||
|
| undefined) || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'supplier_ids',
|
||||||
|
Array.isArray(val) ? val : val ? [val] : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingSuppliers}
|
||||||
|
isClearable
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Product Filter */}
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Produk'
|
||||||
|
placeholder='Pilih Produk'
|
||||||
|
options={productOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.product_ids as
|
||||||
|
| { value: number; label: string }
|
||||||
|
| { value: number; label: string }[]
|
||||||
|
| null
|
||||||
|
| undefined) || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'product_ids',
|
||||||
|
Array.isArray(val) ? val : val ? [val] : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingProducts}
|
||||||
|
isClearable
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Product Category Filter */}
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Kategori Produk'
|
||||||
|
placeholder='Pilih Kategori Produk'
|
||||||
|
options={productCategoryOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.product_category_ids as
|
||||||
|
| { value: number; label: string }
|
||||||
|
| { value: number; label: string }[]
|
||||||
|
| null
|
||||||
|
| undefined) || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'product_category_ids',
|
||||||
|
Array.isArray(val) ? val : val ? [val] : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingProductCategories}
|
||||||
|
isClearable
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Filter By Type */}
|
||||||
|
<SelectInputRadio
|
||||||
|
label='Filter Berdasarkan'
|
||||||
|
placeholder='Pilih Filter Berdasarkan'
|
||||||
|
options={dataTypeOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.filter_by as
|
||||||
|
| { value: string; label: string }
|
||||||
|
| null
|
||||||
|
| undefined) || null
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (!Array.isArray(val)) {
|
||||||
|
formik.setFieldValue('filter_by', val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
isClearable={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Sort By */}
|
||||||
|
<SelectInputRadio
|
||||||
|
label='Urutkan Berdasarkan'
|
||||||
|
placeholder='Pilih Urutkan Berdasarkan'
|
||||||
|
options={sortByOptions}
|
||||||
|
value={
|
||||||
|
(formik.values.sort_by as
|
||||||
|
| { value: string; label: string }
|
||||||
|
| null
|
||||||
|
| undefined) || null
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (!Array.isArray(val)) {
|
||||||
|
formik.setFieldValue('sort_by', val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
isClearable={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Area Filter */}
|
{/* Modal Footer */}
|
||||||
<SelectInputCheckbox
|
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
|
||||||
label='Area'
|
<Button
|
||||||
placeholder='Pilih Area'
|
type='reset'
|
||||||
options={areaOptions}
|
variant='soft'
|
||||||
value={filterArea}
|
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
|
||||||
onChange={(val) => {
|
>
|
||||||
setFilterArea(Array.isArray(val) ? val : val ? [val] : []);
|
Reset Filter
|
||||||
}}
|
</Button>
|
||||||
isLoading={isLoadingAreas}
|
<Button
|
||||||
isClearable
|
type='submit'
|
||||||
className={{ wrapper: 'w-full' }}
|
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||||
/>
|
disabled={hasDateError || !formik.isValid || formik.isSubmitting}
|
||||||
|
>
|
||||||
{/* Supplier Filter */}
|
Apply Filter
|
||||||
<SelectInputCheckbox
|
</Button>
|
||||||
label='Supplier'
|
</div>
|
||||||
placeholder='Pilih Supplier'
|
</form>
|
||||||
options={supplierOptions}
|
|
||||||
value={filterSupplier}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFilterSupplier(Array.isArray(val) ? val : val ? [val] : []);
|
|
||||||
}}
|
|
||||||
isLoading={isLoadingSuppliers}
|
|
||||||
isClearable
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Product Filter */}
|
|
||||||
<SelectInputCheckbox
|
|
||||||
label='Produk'
|
|
||||||
placeholder='Pilih Produk'
|
|
||||||
options={productOptions}
|
|
||||||
value={filterProduct}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFilterProduct(Array.isArray(val) ? val : val ? [val] : []);
|
|
||||||
}}
|
|
||||||
isLoading={isLoadingProducts}
|
|
||||||
isClearable
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Product Category Filter */}
|
|
||||||
<SelectInputCheckbox
|
|
||||||
label='Kategori Produk'
|
|
||||||
placeholder='Pilih Kategori Produk'
|
|
||||||
options={productCategoryOptions}
|
|
||||||
value={filterProductCategory}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFilterProductCategory(
|
|
||||||
Array.isArray(val) ? val : val ? [val] : []
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isLoading={isLoadingProductCategories}
|
|
||||||
isClearable
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Filter By Type */}
|
|
||||||
<SelectInputRadio
|
|
||||||
label='Filter Berdasarkan'
|
|
||||||
placeholder='Pilih Filter Berdasarkan'
|
|
||||||
options={dataTypeOptions}
|
|
||||||
value={filterByType}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (!Array.isArray(val)) {
|
|
||||||
setFilterByType(val);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
isClearable={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Sort By */}
|
|
||||||
<SelectInputRadio
|
|
||||||
label='Urutkan Berdasarkan'
|
|
||||||
placeholder='Pilih Urutkan Berdasarkan'
|
|
||||||
options={sortByOptions}
|
|
||||||
value={filterSortBy}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (!Array.isArray(val)) {
|
|
||||||
setFilterSortBy(val);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
isClearable={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Modal Footer */}
|
|
||||||
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
|
|
||||||
<Button
|
|
||||||
variant='soft'
|
|
||||||
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
|
|
||||||
onClick={handleResetFilters}
|
|
||||||
>
|
|
||||||
Reset Filter
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
|
||||||
onClick={handleApplyFilters}
|
|
||||||
disabled={hasDateError}
|
|
||||||
>
|
|
||||||
Apply Filter
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user