mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'feat/customer-control-ui-adjustment' into 'development'
[FEAT/FE] Customer Control UI Adjustment (Kontrol Pembayaran Customer) See merge request mbugroup/lti-web-client!174
This commit is contained in:
@@ -148,7 +148,11 @@ const Card = ({
|
|||||||
const hasContent = children || actions || footer;
|
const hasContent = children || actions || footer;
|
||||||
|
|
||||||
const titleContent = (
|
const titleContent = (
|
||||||
<div className='group flex items-center !justify-between w-full'>
|
<div
|
||||||
|
className={
|
||||||
|
`group flex items-center justify-between! w-full` + getTitleClasses()
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
||||||
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
||||||
@@ -156,7 +160,7 @@ const Card = ({
|
|||||||
{collapsible && (
|
{collapsible && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCollapsedChange(!isCollapsed)}
|
onClick={() => handleCollapsedChange(!isCollapsed)}
|
||||||
className='btn btn-ghost btn-sm btn-circle'
|
className={`btn btn-ghost btn-sm btn-circle` + getTitleClasses()}
|
||||||
aria-label={isCollapsed ? 'Expand content' : 'Collapse content'}
|
aria-label={isCollapsed ? 'Expand content' : 'Collapse content'}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ interface SelectInputBaseProps<T = OptionType> {
|
|||||||
bottomLabel?: ReactNode;
|
bottomLabel?: ReactNode;
|
||||||
options: T[];
|
options: T[];
|
||||||
optionComponent?: OptionComponent<T>;
|
optionComponent?: OptionComponent<T>;
|
||||||
|
components?: Partial<typeof ReactSelectComponents>;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
isClearable?: boolean;
|
isClearable?: boolean;
|
||||||
@@ -61,10 +62,13 @@ interface SelectInputBaseProps<T = OptionType> {
|
|||||||
onInputChange?: (search: string) => void;
|
onInputChange?: (search: string) => void;
|
||||||
startAdornment?: ReactNode;
|
startAdornment?: ReactNode;
|
||||||
menuPortalTarget?: HTMLElement | null;
|
menuPortalTarget?: HTMLElement | null;
|
||||||
|
closeMenuOnSelect?: boolean;
|
||||||
|
hideSelectedOptions?: boolean;
|
||||||
onMenuScrollToBottom?: ((event: WheelEvent | TouchEvent) => void) | undefined;
|
onMenuScrollToBottom?: ((event: WheelEvent | TouchEvent) => void) | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectInputProps<T = OptionType> extends SelectInputBaseProps<T> {
|
export interface SelectInputProps<T = OptionType>
|
||||||
|
extends SelectInputBaseProps<T> {
|
||||||
createables?: boolean;
|
createables?: boolean;
|
||||||
value?: T | T[] | null;
|
value?: T | T[] | null;
|
||||||
onChange?: (val: T | T[] | null) => void;
|
onChange?: (val: T | T[] | null) => void;
|
||||||
@@ -130,6 +134,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
optionComponent,
|
optionComponent,
|
||||||
|
components: customComponents,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
isClearable,
|
isClearable,
|
||||||
@@ -148,6 +153,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
onInputChange,
|
onInputChange,
|
||||||
startAdornment,
|
startAdornment,
|
||||||
menuPortalTarget,
|
menuPortalTarget,
|
||||||
|
closeMenuOnSelect,
|
||||||
|
hideSelectedOptions,
|
||||||
onMenuScrollToBottom,
|
onMenuScrollToBottom,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -158,14 +165,18 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
|
|
||||||
const components = useMemo(() => {
|
const components = useMemo(() => {
|
||||||
const base = isAnimated ? animatedComponents : {};
|
const base = isAnimated ? animatedComponents : {};
|
||||||
const customComponents = { ...base, IndicatorSeparator: () => null };
|
const mergedComponents = { ...base, IndicatorSeparator: () => null };
|
||||||
|
|
||||||
if (startAdornment) {
|
if (startAdornment) {
|
||||||
customComponents.Control = CustomControl;
|
mergedComponents.Control = CustomControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return customComponents;
|
if (customComponents) {
|
||||||
}, [isAnimated, startAdornment]);
|
Object.assign(mergedComponents, customComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedComponents;
|
||||||
|
}, [isAnimated, startAdornment, customComponents]);
|
||||||
|
|
||||||
const internalInputChangeHandler = (val: string, meta: InputActionMeta) => {
|
const internalInputChangeHandler = (val: string, meta: InputActionMeta) => {
|
||||||
if (meta.action === 'input-change') setInternalInputValue(val);
|
if (meta.action === 'input-change') setInternalInputValue(val);
|
||||||
@@ -235,6 +246,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
isRtl={isRtl}
|
isRtl={isRtl}
|
||||||
isSearchable={isSearchable}
|
isSearchable={isSearchable}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
closeMenuOnSelect={closeMenuOnSelect}
|
||||||
|
hideSelectedOptions={hideSelectedOptions}
|
||||||
className={cn('w-full', className?.select)}
|
className={cn('w-full', className?.select)}
|
||||||
classNames={{
|
classNames={{
|
||||||
...(!startAdornment && {
|
...(!startAdornment && {
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
OptionProps,
|
||||||
|
GroupBase,
|
||||||
|
components as ReactSelectComponents,
|
||||||
|
} from 'react-select';
|
||||||
|
import SelectInput, { OptionType, SelectInputProps } from './SelectInput';
|
||||||
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
|
interface SelectInputCheckboxProps<T = OptionType>
|
||||||
|
extends Omit<
|
||||||
|
SelectInputProps<T>,
|
||||||
|
'closeMenuOnSelect' | 'hideSelectedOptions' | 'optionComponent'
|
||||||
|
> {
|
||||||
|
closeMenuOnSelect?: boolean;
|
||||||
|
hideSelectedOptions?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheckboxOption = <
|
||||||
|
T extends OptionType,
|
||||||
|
IsMulti extends boolean,
|
||||||
|
Group extends GroupBase<T>,
|
||||||
|
>(
|
||||||
|
props: OptionProps<T, IsMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { isSelected, label, innerRef, innerProps, className } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={innerRef}
|
||||||
|
{...innerProps}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2 px-3 py-2 cursor-pointer',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={() => null}
|
||||||
|
className='checkbox checkbox-sm checkbox-primary pointer-events-none'
|
||||||
|
/>
|
||||||
|
<label className='cursor-pointer flex-1 select-none'>{label}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectInputCheckbox = <T extends OptionType>(
|
||||||
|
props: SelectInputCheckboxProps<T>
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
closeMenuOnSelect = false,
|
||||||
|
hideSelectedOptions = false,
|
||||||
|
isMulti = true,
|
||||||
|
className,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const customComponents = useMemo(() => {
|
||||||
|
return {
|
||||||
|
Option: CheckboxOption as typeof ReactSelectComponents.Option,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectInput<T>
|
||||||
|
{...restProps}
|
||||||
|
isMulti={isMulti}
|
||||||
|
closeMenuOnSelect={closeMenuOnSelect}
|
||||||
|
hideSelectedOptions={hideSelectedOptions}
|
||||||
|
className={{
|
||||||
|
...className,
|
||||||
|
select: cn(className?.select, 'select-checkbox'),
|
||||||
|
}}
|
||||||
|
components={customComponents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectInputCheckbox;
|
||||||
@@ -7,9 +7,11 @@ import SelectInput, {
|
|||||||
useSelect,
|
useSelect,
|
||||||
OptionType,
|
OptionType,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
import { CustomerApi } from '@/services/api/master-data';
|
import { CustomerApi } from '@/services/api/master-data';
|
||||||
import { FinanceApi } from '@/services/api/report/finance-report';
|
import { FinanceApi } from '@/services/api/report/finance-report';
|
||||||
|
import { UserApi } from '@/services/api/user';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
@@ -18,7 +20,6 @@ import {
|
|||||||
CustomerPaymentSummary,
|
CustomerPaymentSummary,
|
||||||
} from '@/types/api/report/customer-payment';
|
} from '@/types/api/report/customer-payment';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import Pagination from '@/components/Pagination';
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
import MenuItem from '@/components/menu/MenuItem';
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
@@ -37,31 +38,34 @@ const CustomerPaymentTab = () => {
|
|||||||
|
|
||||||
// ===== PAGINATION STATE =====
|
// ===== PAGINATION STATE =====
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize] = useState(10);
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
// ===== SUBMISSION STATE =====
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
|
||||||
// ===== FILTER STATE =====
|
// ===== FILTER STATE =====
|
||||||
const [filterCustomer, setFilterCustomer] = useState<OptionType[]>([]);
|
const [filterCustomer, setFilterCustomer] = useState<typeof customerOptions>(
|
||||||
const [filterSales, setFilterSales] = useState<OptionType[]>([]);
|
[]
|
||||||
|
);
|
||||||
|
const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
|
||||||
const [filterStartDate, setFilterStartDate] = useState('');
|
const [filterStartDate, setFilterStartDate] = useState('');
|
||||||
const [filterEndDate, setFilterEndDate] = useState('');
|
const [filterEndDate, setFilterEndDate] = useState('');
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
|
const {
|
||||||
useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
options: customerOptions,
|
||||||
|
isLoadingOptions: isLoadingCustomers,
|
||||||
|
loadMore: loadMoreCustomers,
|
||||||
|
hasMore: hasMoreCustomers,
|
||||||
|
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const salesOptions = useMemo(
|
const {
|
||||||
() => [
|
options: salesOptions,
|
||||||
{ value: 'Sales A', label: 'Sales A' },
|
isLoadingOptions: isLoadingSales,
|
||||||
{ value: 'Sales B', label: 'Sales B' },
|
loadMore: loadMoreSales,
|
||||||
{ value: 'Sales C', label: 'Sales C' },
|
hasMore: hasMoreSales,
|
||||||
// TODO: Fetch sales options from API
|
} = useSelect(UserApi.basePath, 'id', 'name', 'search');
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataTypeOptions = useMemo(
|
const dataTypeOptions = useMemo(
|
||||||
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
|
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
|
||||||
@@ -115,6 +119,41 @@ const CustomerPaymentTab = () => {
|
|||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
}, [filterModal]);
|
}, [filterModal]);
|
||||||
|
|
||||||
|
// ===== ACTIVE FILTERS COUNT =====
|
||||||
|
const activeFiltersCount = useMemo(() => {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
// Date filter (start_date + end_date = 1 filter)
|
||||||
|
if (filterStartDate || filterEndDate) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customer filter
|
||||||
|
if (filterCustomer.length > 0) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sales filter
|
||||||
|
if (filterSales.length > 0) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by (always count if submitted)
|
||||||
|
if (isSubmitted) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}, [
|
||||||
|
filterStartDate,
|
||||||
|
filterEndDate,
|
||||||
|
filterCustomer,
|
||||||
|
filterSales,
|
||||||
|
isSubmitted,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hasFilters = activeFiltersCount > 0;
|
||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: customerPayment, isLoading } = useSWR(
|
const { data: customerPayment, isLoading } = useSWR(
|
||||||
isSubmitted
|
isSubmitted
|
||||||
@@ -124,7 +163,7 @@ const CustomerPaymentTab = () => {
|
|||||||
filterCustomer.length > 0
|
filterCustomer.length > 0
|
||||||
? filterCustomer.map((v) => String(v.value)).join(',')
|
? filterCustomer.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
sales:
|
sales_id:
|
||||||
filterSales.length > 0
|
filterSales.length > 0
|
||||||
? filterSales.map((v) => String(v.value)).join(',')
|
? filterSales.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -141,7 +180,7 @@ const CustomerPaymentTab = () => {
|
|||||||
([, params]) =>
|
([, params]) =>
|
||||||
FinanceApi.getCustomerPaymentReport(
|
FinanceApi.getCustomerPaymentReport(
|
||||||
params.customer_id,
|
params.customer_id,
|
||||||
params.sales,
|
params.sales_id,
|
||||||
params.filter_by,
|
params.filter_by,
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
@@ -158,11 +197,6 @@ const CustomerPaymentTab = () => {
|
|||||||
[customerPayment]
|
[customerPayment]
|
||||||
);
|
);
|
||||||
|
|
||||||
const meta =
|
|
||||||
isResponseSuccess(customerPayment) && customerPayment?.meta
|
|
||||||
? customerPayment.meta
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// ===== EXPORT DATA FETCHER =====
|
// ===== EXPORT DATA FETCHER =====
|
||||||
const customerPaymentExport = useCallback(async (): Promise<
|
const customerPaymentExport = useCallback(async (): Promise<
|
||||||
CustomerPaymentReport[] | null
|
CustomerPaymentReport[] | null
|
||||||
@@ -172,7 +206,7 @@ const CustomerPaymentTab = () => {
|
|||||||
filterCustomer.length > 0
|
filterCustomer.length > 0
|
||||||
? filterCustomer.map((v) => String(v.value)).join(',')
|
? filterCustomer.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
sales:
|
sales_id:
|
||||||
filterSales.length > 0
|
filterSales.length > 0
|
||||||
? filterSales.map((v) => String(v.value)).join(',')
|
? filterSales.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -185,7 +219,7 @@ const CustomerPaymentTab = () => {
|
|||||||
|
|
||||||
const response = await FinanceApi.getCustomerPaymentReport(
|
const response = await FinanceApi.getCustomerPaymentReport(
|
||||||
params.customer_id,
|
params.customer_id,
|
||||||
params.sales,
|
params.sales_id,
|
||||||
params.filter_by,
|
params.filter_by,
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
@@ -260,27 +294,6 @@ const CustomerPaymentTab = () => {
|
|||||||
}
|
}
|
||||||
}, [customerPaymentExport]);
|
}, [customerPaymentExport]);
|
||||||
|
|
||||||
// ===== PAGINATION HANDLERS =====
|
|
||||||
const handlePageChange = (page: number) => {
|
|
||||||
setCurrentPage(page);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowChange = (pageSize: number) => {
|
|
||||||
setPageSize(pageSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNextPage = () => {
|
|
||||||
if (meta && currentPage < meta.total_pages) {
|
|
||||||
setCurrentPage(currentPage + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePrevPage = () => {
|
|
||||||
if (currentPage > 1) {
|
|
||||||
setCurrentPage(currentPage - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTableColumns = (
|
const getTableColumns = (
|
||||||
summary: CustomerPaymentSummary
|
summary: CustomerPaymentSummary
|
||||||
): ColumnDef<CustomerPaymentReport['rows'][0]>[] => {
|
): ColumnDef<CustomerPaymentReport['rows'][0]>[] => {
|
||||||
@@ -532,14 +545,37 @@ const CustomerPaymentTab = () => {
|
|||||||
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
||||||
>
|
>
|
||||||
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
||||||
<Button variant='outline' onClick={filterModal.openModal}>
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
onClick={filterModal.openModal}
|
||||||
|
className={
|
||||||
|
hasFilters
|
||||||
|
? 'bg-linear-to-b from-[#0069E0]/40 to-white text-[#0069E0] rounded-lg'
|
||||||
|
: 'rounded-lg'
|
||||||
|
}
|
||||||
|
>
|
||||||
<Icon icon='heroicons:funnel' width={18} height={18} />
|
<Icon icon='heroicons:funnel' width={18} height={18} />
|
||||||
Filter
|
Filter
|
||||||
|
{hasFilters && (
|
||||||
|
<Badge
|
||||||
|
variant='default'
|
||||||
|
className={{
|
||||||
|
badge:
|
||||||
|
'rounded-lg px-1.5 py-2.5 text-xs font-semibold bg-error text-white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{activeFiltersCount}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant='outline' isLoading={isAnyExportLoading}>
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
isLoading={isAnyExportLoading}
|
||||||
|
className='rounded-lg'
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='heroicons:cloud-arrow-down'
|
icon='heroicons:cloud-arrow-down'
|
||||||
width={18}
|
width={18}
|
||||||
@@ -550,7 +586,7 @@ const CustomerPaymentTab = () => {
|
|||||||
}
|
}
|
||||||
align='end'
|
align='end'
|
||||||
>
|
>
|
||||||
<Menu>
|
<Menu className={'w-full'}>
|
||||||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||||||
<MenuItem title='PDF' onClick={handleExportPdf} />
|
<MenuItem title='PDF' onClick={handleExportPdf} />
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -608,10 +644,9 @@ const CustomerPaymentTab = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInputCheckbox
|
||||||
label='Customer'
|
label='Customer'
|
||||||
placeholder='Pilih Customer'
|
placeholder='Pilih Customer'
|
||||||
isMulti
|
|
||||||
options={customerOptions}
|
options={customerOptions}
|
||||||
value={filterCustomer}
|
value={filterCustomer}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@@ -621,21 +656,23 @@ const CustomerPaymentTab = () => {
|
|||||||
}}
|
}}
|
||||||
isLoading={isLoadingCustomers}
|
isLoading={isLoadingCustomers}
|
||||||
isClearable
|
isClearable
|
||||||
|
onMenuScrollToBottom={loadMoreCustomers}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInputCheckbox
|
||||||
label='Sales'
|
label='Sales'
|
||||||
placeholder='Pilih Sales'
|
placeholder='Pilih Sales'
|
||||||
isMulti
|
|
||||||
options={salesOptions}
|
options={salesOptions}
|
||||||
value={filterSales}
|
value={filterSales}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
|
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
|
||||||
}}
|
}}
|
||||||
|
isLoading={isLoadingSales}
|
||||||
isClearable
|
isClearable
|
||||||
|
onMenuScrollToBottom={loadMoreSales}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -704,8 +741,12 @@ const CustomerPaymentTab = () => {
|
|||||||
<Card
|
<Card
|
||||||
key={customerReport.customer.id}
|
key={customerReport.customer.id}
|
||||||
title={customerReport.customer.name}
|
title={customerReport.customer.name}
|
||||||
subtitle={`${customerReport.customer.address || ''}`}
|
className={{
|
||||||
className={{ wrapper: 'w-full' }}
|
wrapper: 'w-full rounded-2xl',
|
||||||
|
body: 'p-0',
|
||||||
|
title:
|
||||||
|
'py-1.5 px-3 bg-[#0069E0] text-white text-lg font-normal',
|
||||||
|
}}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
>
|
>
|
||||||
@@ -716,7 +757,7 @@ const CustomerPaymentTab = () => {
|
|||||||
renderFooter={customerReport.rows.length > 0}
|
renderFooter={customerReport.rows.length > 0}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: 'w-full',
|
containerClassName: 'w-full',
|
||||||
tableWrapperClassName: 'overflow-x-auto mt-4',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||||
headerColumnClassName:
|
headerColumnClassName:
|
||||||
@@ -738,20 +779,6 @@ const CustomerPaymentTab = () => {
|
|||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
{meta && data.length > 0 && (
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Pagination
|
|
||||||
currentPage={meta.page}
|
|
||||||
totalItems={meta.total_results}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onRowChange={handleRowChange}
|
|
||||||
onNextPage={handleNextPage}
|
|
||||||
onPrevPage={handlePrevPage}
|
|
||||||
rowOptions={[10, 25, 50, 100]}
|
|
||||||
itemsPerPage={meta.limit}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class FinanceApiService extends BaseApiService<
|
|||||||
|
|
||||||
async getCustomerPaymentReport(
|
async getCustomerPaymentReport(
|
||||||
customer_id?: string,
|
customer_id?: string,
|
||||||
sales?: string,
|
sales_id?: string,
|
||||||
filter_by?: 'do_date',
|
filter_by?: 'do_date',
|
||||||
start_date?: string,
|
start_date?: string,
|
||||||
end_date?: string,
|
end_date?: string,
|
||||||
@@ -27,7 +27,7 @@ export class FinanceApiService extends BaseApiService<
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
customer_id: customer_id,
|
customer_id: customer_id,
|
||||||
sales: sales,
|
sales_id: sales_id,
|
||||||
filter_by: filter_by,
|
filter_by: filter_by,
|
||||||
start_date: start_date,
|
start_date: start_date,
|
||||||
end_date: end_date,
|
end_date: end_date,
|
||||||
|
|||||||
Reference in New Issue
Block a user