mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-web-client into fix/dashboard
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
|
||||||
|
|||||||
@@ -9,15 +9,20 @@ import Select, {
|
|||||||
SingleValue,
|
SingleValue,
|
||||||
components as ReactSelectComponents,
|
components as ReactSelectComponents,
|
||||||
ControlProps,
|
ControlProps,
|
||||||
|
MenuListProps,
|
||||||
} from 'react-select';
|
} from 'react-select';
|
||||||
import CreatableSelect from 'react-select/creatable';
|
import CreatableSelect from 'react-select/creatable';
|
||||||
import makeAnimated from 'react-select/animated';
|
import makeAnimated from 'react-select/animated';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { cn, getByPath } from '@/lib/helper';
|
import { cn, getByPath } from '@/lib/helper';
|
||||||
import useSWR from 'swr';
|
import useSWRInfinite from 'swr/infinite';
|
||||||
import { httpClientFetcher } from '@/services/http/client';
|
import { httpClientFetcher } from '@/services/http/client';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import {
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
BaseApiResponse,
|
||||||
|
ErrorApiResponse,
|
||||||
|
SuccessApiResponse,
|
||||||
|
} from '@/types/api/api-general';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
|
||||||
export interface OptionType {
|
export interface OptionType {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
@@ -35,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;
|
||||||
@@ -56,9 +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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -93,6 +103,29 @@ const CustomControl = <
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CustomMenuList = <
|
||||||
|
Option,
|
||||||
|
IsMulti extends boolean,
|
||||||
|
Group extends GroupBase<Option>,
|
||||||
|
>(
|
||||||
|
props: MenuListProps<Option, IsMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { children, selectProps, options } = props;
|
||||||
|
const { isLoading } = selectProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactSelectComponents.MenuList {...props}>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{options.length > 0 && isLoading && (
|
||||||
|
<div className='mt-1 px-3 py-2 rounded-md text-center text-gray-400'>
|
||||||
|
<span className='loading loading-spinner loading-md' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ReactSelectComponents.MenuList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
@@ -101,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,
|
||||||
@@ -119,6 +153,9 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
onInputChange,
|
onInputChange,
|
||||||
startAdornment,
|
startAdornment,
|
||||||
menuPortalTarget,
|
menuPortalTarget,
|
||||||
|
closeMenuOnSelect,
|
||||||
|
hideSelectedOptions,
|
||||||
|
onMenuScrollToBottom,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [internalInputValue, setInternalInputValue] = useState('');
|
const [internalInputValue, setInternalInputValue] = useState('');
|
||||||
@@ -128,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);
|
||||||
@@ -205,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 && {
|
||||||
@@ -256,6 +299,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
components={{
|
components={{
|
||||||
...components,
|
...components,
|
||||||
...(optionComponent ? { Option: optionComponent } : {}),
|
...(optionComponent ? { Option: optionComponent } : {}),
|
||||||
|
MenuList: CustomMenuList,
|
||||||
}}
|
}}
|
||||||
{...(startAdornment && {
|
{...(startAdornment && {
|
||||||
shouldShowAdornment,
|
shouldShowAdornment,
|
||||||
@@ -269,6 +313,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
styles={{
|
styles={{
|
||||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
}}
|
}}
|
||||||
|
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||||
@@ -288,34 +333,96 @@ const useSelect = <T,>(
|
|||||||
) => {
|
) => {
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
const optionsUrlParams = useMemo(() => {
|
const pageKey = 'page';
|
||||||
return new URLSearchParams({
|
const limitKey = 'limit';
|
||||||
|
const limit = params?.['limit'] ?? 10;
|
||||||
|
|
||||||
|
const getKey = (
|
||||||
|
pageIndex: number,
|
||||||
|
previousPageData?: BaseApiResponse<T[]>
|
||||||
|
) => {
|
||||||
|
// stop when backend says no more pages
|
||||||
|
if (previousPageData && isResponseSuccess(previousPageData)) {
|
||||||
|
const meta = previousPageData.meta;
|
||||||
|
if (meta && meta.page >= meta.total_pages) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qs = new URLSearchParams({
|
||||||
|
...(params ?? {}),
|
||||||
[searchKey]: inputValue ?? '',
|
[searchKey]: inputValue ?? '',
|
||||||
...params,
|
[pageKey]: String(pageIndex + 1),
|
||||||
|
[limitKey]: String(limit),
|
||||||
}).toString();
|
}).toString();
|
||||||
}, [inputValue, searchKey, params]);
|
|
||||||
|
|
||||||
const optionsUrl = `${basePath}?${optionsUrlParams}`;
|
return `${basePath}?${qs}`;
|
||||||
|
};
|
||||||
|
|
||||||
const { data, isLoading } = useSWR(optionsUrl, async (url) => {
|
const {
|
||||||
return await httpClientFetcher<BaseApiResponse<T[]>>(url);
|
data: pages,
|
||||||
});
|
isLoading,
|
||||||
|
isValidating,
|
||||||
|
size,
|
||||||
|
setSize,
|
||||||
|
} = useSWRInfinite<BaseApiResponse<T[]>>(getKey, (url) =>
|
||||||
|
httpClientFetcher<BaseApiResponse<T[]>>(url)
|
||||||
|
);
|
||||||
|
|
||||||
const options = isResponseSuccess(data)
|
const options = useMemo(() => {
|
||||||
? data.data.map((item) => {
|
if (!pages) return [];
|
||||||
return {
|
|
||||||
|
return pages.flatMap((page) =>
|
||||||
|
isResponseSuccess(page)
|
||||||
|
? page.data.map((item) => ({
|
||||||
value: getByPath<T, number>(item, valueKey as string),
|
value: getByPath<T, number>(item, valueKey as string),
|
||||||
label: getByPath<T, string>(item, labelKey as string),
|
label: getByPath<T, string>(item, labelKey as string),
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
}, [pages, valueKey, labelKey]);
|
||||||
|
|
||||||
|
const lastPage = pages?.[pages.length - 1];
|
||||||
|
const hasMore =
|
||||||
|
!!lastPage &&
|
||||||
|
isResponseSuccess(lastPage) &&
|
||||||
|
!!lastPage.meta &&
|
||||||
|
lastPage.meta.page < lastPage.meta.total_pages;
|
||||||
|
|
||||||
|
const loadMore = () => {
|
||||||
|
if (!hasMore) return;
|
||||||
|
setSize(size + 1);
|
||||||
};
|
};
|
||||||
})
|
|
||||||
: [];
|
let formattedSuccessRawData: SuccessApiResponse<T[]> | undefined = undefined;
|
||||||
|
let formattedErrorRawData: ErrorApiResponse | undefined = undefined;
|
||||||
|
|
||||||
|
const latestPagesIndex = pages?.length ? pages.length - 1 : 0;
|
||||||
|
|
||||||
|
if (isResponseSuccess(pages?.[latestPagesIndex])) {
|
||||||
|
formattedSuccessRawData = {
|
||||||
|
...pages?.[latestPagesIndex],
|
||||||
|
data:
|
||||||
|
pages?.flatMap((page) => (isResponseSuccess(page) ? page.data : [])) ??
|
||||||
|
[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResponseError(pages?.[latestPagesIndex])) {
|
||||||
|
formattedErrorRawData = pages?.[latestPagesIndex];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
|
|
||||||
options,
|
options,
|
||||||
isLoadingOptions: isLoading,
|
rawData: isResponseSuccess(pages?.[latestPagesIndex])
|
||||||
rawData: data,
|
? formattedSuccessRawData
|
||||||
|
: formattedErrorRawData,
|
||||||
|
|
||||||
|
isLoadingOptions: isLoading || isValidating,
|
||||||
|
isLoadingMore: isValidating && size > 1,
|
||||||
|
hasMore,
|
||||||
|
loadMore,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -23,13 +23,17 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import TextArea from '@/components/input/TextArea';
|
import TextArea from '@/components/input/TextArea';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { UserApi } from '@/services/api/user';
|
import { UserApi } from '@/services/api/user';
|
||||||
import { TYPE_OPTIONS } from '@/config/constant';
|
import { TYPE_OPTIONS } from '@/config/constant';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { User } from '@/types/api/api-general';
|
||||||
|
|
||||||
interface CustomerFormProps {
|
interface CustomerFormProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
@@ -47,25 +51,15 @@ const CustomerForm = ({
|
|||||||
// Setup State
|
// Setup State
|
||||||
const [customerFormErrorMessage, setCustomerFormErrorMessage] = useState('');
|
const [customerFormErrorMessage, setCustomerFormErrorMessage] = useState('');
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [picSelectInputValue, setPicSelectInputValue] = useState('');
|
|
||||||
|
|
||||||
// Fetch Data
|
const {
|
||||||
const picUrl = `${UserApi.basePath}?${new URLSearchParams({
|
setInputValue: setPicSelectInputValue,
|
||||||
search: picSelectInputValue ?? '',
|
options: picOptions,
|
||||||
})}`;
|
isLoadingOptions: isLoadingPicOptions,
|
||||||
|
loadMore: loadMorePic,
|
||||||
const { data: pic, isLoading: isLoadingPic } = useSWR(
|
} = useSelect<User>(UserApi.basePath, 'id', 'name');
|
||||||
picUrl,
|
|
||||||
UserApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
// -- Options data mapping
|
// -- Options data mapping
|
||||||
const picOptions = isResponseSuccess(pic)
|
|
||||||
? pic?.data.map((area) => ({
|
|
||||||
value: area.id,
|
|
||||||
label: area.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
const typeOptions = TYPE_OPTIONS;
|
const typeOptions = TYPE_OPTIONS;
|
||||||
|
|
||||||
// Handler Event
|
// Handler Event
|
||||||
@@ -240,11 +234,12 @@ const CustomerForm = ({
|
|||||||
required
|
required
|
||||||
placeholder='Pilih PIC'
|
placeholder='Pilih PIC'
|
||||||
label='PIC'
|
label='PIC'
|
||||||
value={formik.values.pic ?? undefined}
|
value={formik.values.pic?.value ? formik.values.pic : undefined}
|
||||||
onChange={picChangeHandler}
|
onChange={picChangeHandler}
|
||||||
options={picOptions}
|
options={picOptions}
|
||||||
onInputChange={setPicSelectInputValue}
|
onInputChange={setPicSelectInputValue}
|
||||||
isLoading={isLoadingPic}
|
onMenuScrollToBottom={loadMorePic}
|
||||||
|
isLoading={isLoadingPicOptions}
|
||||||
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
||||||
errorMessage={formik.errors.picId as string}
|
errorMessage={formik.errors.picId as string}
|
||||||
isDisabled={formType === 'detail'}
|
isDisabled={formType === 'detail'}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
@@ -31,6 +34,7 @@ import { UserApi } from '@/services/api/user';
|
|||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { User } from '@/types/api/api-general';
|
||||||
|
|
||||||
interface KandangFormProps {
|
interface KandangFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -128,23 +132,12 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
|||||||
const { setValues: formikSetValues } = formik;
|
const { setValues: formikSetValues } = formik;
|
||||||
|
|
||||||
// location
|
// location
|
||||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setLocationSelectInputValue,
|
||||||
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
options: locationOptions,
|
||||||
search: locationSelectInputValue ?? '',
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreLocations,
|
||||||
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||||
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
|
||||||
locationsUrl,
|
|
||||||
LocationApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const locationOptions = isResponseSuccess(locations)
|
|
||||||
? locations?.data.map((location) => ({
|
|
||||||
value: location.id,
|
|
||||||
label: location.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('location', true);
|
formik.setFieldTouched('location', true);
|
||||||
@@ -155,23 +148,12 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// PIC
|
// PIC
|
||||||
const [picSelectInputValue, setPicSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setPicSelectInputValue,
|
||||||
const picsUrl = `${UserApi.basePath}?${new URLSearchParams({
|
options: picOptions,
|
||||||
search: picSelectInputValue ?? '',
|
isLoadingOptions: isLoadingPicOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMorePics,
|
||||||
|
} = useSelect<User>(UserApi.basePath, 'id', 'name');
|
||||||
const { data: pics, isLoading: isLoadingPics } = useSWR(
|
|
||||||
picsUrl,
|
|
||||||
LocationApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const picOptions = isResponseSuccess(pics)
|
|
||||||
? pics?.data.map((pic) => ({
|
|
||||||
value: pic.id,
|
|
||||||
label: pic.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const picChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const picChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('pic', true);
|
formik.setFieldTouched('pic', true);
|
||||||
@@ -249,7 +231,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
|||||||
onChange={locationChangeHandler}
|
onChange={locationChangeHandler}
|
||||||
options={locationOptions}
|
options={locationOptions}
|
||||||
onInputChange={setLocationSelectInputValue}
|
onInputChange={setLocationSelectInputValue}
|
||||||
isLoading={isLoadingLocations}
|
onMenuScrollToBottom={loadMoreLocations}
|
||||||
|
isLoading={isLoadingLocationOptions}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.locationId && Boolean(formik.errors.locationId)
|
formik.touched.locationId && Boolean(formik.errors.locationId)
|
||||||
}
|
}
|
||||||
@@ -280,7 +263,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
|||||||
onChange={picChangeHandler}
|
onChange={picChangeHandler}
|
||||||
options={picOptions}
|
options={picOptions}
|
||||||
onInputChange={setPicSelectInputValue}
|
onInputChange={setPicSelectInputValue}
|
||||||
isLoading={isLoadingPics}
|
onMenuScrollToBottom={loadMorePics}
|
||||||
|
isLoading={isLoadingPicOptions}
|
||||||
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
||||||
errorMessage={formik.errors.picId as string}
|
errorMessage={formik.errors.picId as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
@@ -29,6 +32,7 @@ import { AreaApi, LocationApi } from '@/services/api/master-data';
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { Area } from '@/types/api/master-data/area';
|
||||||
|
|
||||||
interface LocationFormProps {
|
interface LocationFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -117,23 +121,12 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
|
|||||||
|
|
||||||
const { setValues: formikSetValues } = formik;
|
const { setValues: formikSetValues } = formik;
|
||||||
|
|
||||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setAreaSelectInputValue,
|
||||||
const areasUrl = `${AreaApi.basePath}?${new URLSearchParams({
|
options: areaOptions,
|
||||||
search: areaSelectInputValue ?? '',
|
isLoadingOptions: isLoadingAreaOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreAreas,
|
||||||
|
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
|
||||||
const { data: areas, isLoading: isLoadingAreas } = useSWR(
|
|
||||||
areasUrl,
|
|
||||||
AreaApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const areaOptions = isResponseSuccess(areas)
|
|
||||||
? areas?.data.map((area) => ({
|
|
||||||
value: area.id,
|
|
||||||
label: area.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('area', true);
|
formik.setFieldTouched('area', true);
|
||||||
@@ -224,7 +217,8 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
|
|||||||
onChange={areaChangeHandler}
|
onChange={areaChangeHandler}
|
||||||
options={areaOptions}
|
options={areaOptions}
|
||||||
onInputChange={setAreaSelectInputValue}
|
onInputChange={setAreaSelectInputValue}
|
||||||
isLoading={isLoadingAreas}
|
onMenuScrollToBottom={loadMoreAreas}
|
||||||
|
isLoading={isLoadingAreaOptions}
|
||||||
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
||||||
errorMessage={formik.errors.areaId as string}
|
errorMessage={formik.errors.areaId as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
@@ -31,6 +34,8 @@ import { flags } from '@/types/api/api-general';
|
|||||||
import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant';
|
import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
|
import { Uom } from '@/types/api/master-data/uom';
|
||||||
|
|
||||||
interface NonstockFormProps {
|
interface NonstockFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -129,23 +134,12 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
const { setValues: formikSetValues } = formik;
|
const { setValues: formikSetValues } = formik;
|
||||||
|
|
||||||
// UOM
|
// UOM
|
||||||
const [uomSelectInputValue, setUomSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setUomSelectInputValue,
|
||||||
const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({
|
options: uomOptions,
|
||||||
search: uomSelectInputValue ?? '',
|
isLoadingOptions: isLoadingUomOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreUoms,
|
||||||
|
} = useSelect<Uom>(UomApi.basePath, 'id', 'name');
|
||||||
const { data: uoms, isLoading: isLoadingUoms } = useSWR(
|
|
||||||
uomsUrl,
|
|
||||||
UomApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const uomOptions = isResponseSuccess(uoms)
|
|
||||||
? uoms?.data.map((uom) => ({
|
|
||||||
value: uom.id,
|
|
||||||
label: uom.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('uom', true);
|
formik.setFieldTouched('uom', true);
|
||||||
@@ -156,25 +150,12 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// supplier
|
// supplier
|
||||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setSupplierSelectInputValue,
|
||||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({
|
options: supplierOptions,
|
||||||
search: supplierSelectInputValue ?? '',
|
isLoadingOptions: isLoadingSupplierOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreSuppliers,
|
||||||
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
|
||||||
suppliersUrl,
|
|
||||||
SupplierApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const supplierOptions = isResponseSuccess(suppliers)
|
|
||||||
? suppliers?.data
|
|
||||||
.filter((sup) => sup.category === 'BOP')
|
|
||||||
.map((supplier) => ({
|
|
||||||
value: supplier.id,
|
|
||||||
label: supplier.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('suppliers', true);
|
formik.setFieldTouched('suppliers', true);
|
||||||
@@ -264,7 +245,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
onChange={uomChangeHandler}
|
onChange={uomChangeHandler}
|
||||||
options={uomOptions}
|
options={uomOptions}
|
||||||
onInputChange={setUomSelectInputValue}
|
onInputChange={setUomSelectInputValue}
|
||||||
isLoading={isLoadingUoms}
|
isLoading={isLoadingUomOptions}
|
||||||
|
onMenuScrollToBottom={loadMoreUoms}
|
||||||
isError={formik.touched.uomId && Boolean(formik.errors.uomId)}
|
isError={formik.touched.uomId && Boolean(formik.errors.uomId)}
|
||||||
errorMessage={formik.errors.uomId as string}
|
errorMessage={formik.errors.uomId as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
@@ -278,7 +260,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
onChange={supplierChangeHandler}
|
onChange={supplierChangeHandler}
|
||||||
options={supplierOptions ?? []}
|
options={supplierOptions ?? []}
|
||||||
onInputChange={setSupplierSelectInputValue}
|
onInputChange={setSupplierSelectInputValue}
|
||||||
isLoading={isLoadingSuppliers}
|
onMenuScrollToBottom={loadMoreSuppliers}
|
||||||
|
isLoading={isLoadingSupplierOptions}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.suppliers && Boolean(formik.errors.suppliers)
|
formik.touched.suppliers && Boolean(formik.errors.suppliers)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
|
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
|
|
||||||
interface ProductFormProps {
|
interface ProductFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -145,6 +146,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
setInputValue: setUomSelectInputValue,
|
setInputValue: setUomSelectInputValue,
|
||||||
options: uomOptions,
|
options: uomOptions,
|
||||||
isLoadingOptions: isLoadingUoms,
|
isLoadingOptions: isLoadingUoms,
|
||||||
|
loadMore: loadMoreUoms,
|
||||||
} = useSelect(UomApi.basePath, 'id', 'name');
|
} = useSelect(UomApi.basePath, 'id', 'name');
|
||||||
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('uom', true);
|
formik.setFieldTouched('uom', true);
|
||||||
@@ -158,6 +160,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
setInputValue: setCategorySelectInputValue,
|
setInputValue: setCategorySelectInputValue,
|
||||||
options: categoryOptions,
|
options: categoryOptions,
|
||||||
isLoadingOptions: isLoadingCategories,
|
isLoadingOptions: isLoadingCategories,
|
||||||
|
loadMore: loadMoreCategories,
|
||||||
} = useSelect(ProductCategoryApi.basePath, 'id', 'name');
|
} = useSelect(ProductCategoryApi.basePath, 'id', 'name');
|
||||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('product_category', true);
|
formik.setFieldTouched('product_category', true);
|
||||||
@@ -167,17 +170,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Supplier (multi select) - using SWR to filter by category
|
// Supplier (multi select) - using SWR to filter by category
|
||||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
const {
|
||||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
|
setInputValue: setSupplierSelectInputValue,
|
||||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
options: supplierOptions,
|
||||||
suppliersUrl,
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
SupplierApi.getAllFetcher
|
loadMore: loadMoreSuppliers,
|
||||||
);
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
||||||
const supplierOptions = isResponseSuccess(suppliers)
|
category: 'SAPRONAK',
|
||||||
? suppliers?.data
|
});
|
||||||
.filter((sup) => sup.category === 'SAPRONAK')
|
|
||||||
.map((sup) => ({ value: sup.id, label: sup.name }))
|
|
||||||
: [];
|
|
||||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||||
formik.setFieldTouched('supplier_ids', true);
|
formik.setFieldTouched('supplier_ids', true);
|
||||||
@@ -291,6 +292,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
onChange={uomChangeHandler}
|
onChange={uomChangeHandler}
|
||||||
options={uomOptions}
|
options={uomOptions}
|
||||||
onInputChange={setUomSelectInputValue}
|
onInputChange={setUomSelectInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreUoms}
|
||||||
isLoading={isLoadingUoms}
|
isLoading={isLoadingUoms}
|
||||||
isError={
|
isError={
|
||||||
(formik.touched.uom || formik.touched.uom_id) &&
|
(formik.touched.uom || formik.touched.uom_id) &&
|
||||||
@@ -308,6 +310,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
onChange={categoryChangeHandler}
|
onChange={categoryChangeHandler}
|
||||||
options={categoryOptions}
|
options={categoryOptions}
|
||||||
onInputChange={setCategorySelectInputValue}
|
onInputChange={setCategorySelectInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreCategories}
|
||||||
isLoading={isLoadingCategories}
|
isLoading={isLoadingCategories}
|
||||||
isError={
|
isError={
|
||||||
(formik.touched.product_category ||
|
(formik.touched.product_category ||
|
||||||
@@ -412,6 +415,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
onChange={supplierChangeHandler}
|
onChange={supplierChangeHandler}
|
||||||
options={supplierOptions}
|
options={supplierOptions}
|
||||||
onInputChange={setSupplierSelectInputValue}
|
onInputChange={setSupplierSelectInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreSuppliers}
|
||||||
isLoading={isLoadingSuppliers}
|
isLoading={isLoadingSuppliers}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.supplier_ids &&
|
formik.touched.supplier_ids &&
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
@@ -35,6 +38,8 @@ import { cn } from '@/lib/helper';
|
|||||||
import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant';
|
import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant';
|
||||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { Area } from '@/types/api/master-data/area';
|
||||||
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
|
|
||||||
interface WarehouseFormProps {
|
interface WarehouseFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -221,61 +226,28 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
|||||||
const { setValues: formikSetValues } = formik;
|
const { setValues: formikSetValues } = formik;
|
||||||
|
|
||||||
// Area
|
// Area
|
||||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setAreaSelectInputValue,
|
||||||
const areasUrl = `${AreaApi.basePath}?${new URLSearchParams({
|
options: areaOptions,
|
||||||
search: areaSelectInputValue ?? '',
|
isLoadingOptions: isLoadingAreaOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreAreas,
|
||||||
|
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
|
||||||
const { data: areas, isLoading: isLoadingAreas } = useSWR(
|
|
||||||
areasUrl,
|
|
||||||
AreaApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const areaOptions = isResponseSuccess(areas)
|
|
||||||
? areas?.data.map((area) => ({
|
|
||||||
value: area.id,
|
|
||||||
label: area.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setLocationSelectInputValue,
|
||||||
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
options: locationOptions,
|
||||||
search: locationSelectInputValue ?? '',
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreLocations,
|
||||||
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||||
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
|
||||||
locationsUrl,
|
|
||||||
LocationApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const locationOptions = isResponseSuccess(locations)
|
|
||||||
? locations?.data.map((location) => ({
|
|
||||||
value: location.id,
|
|
||||||
label: location.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Kandang
|
// Kandang
|
||||||
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
|
const {
|
||||||
|
setInputValue: setKandangSelectInputValue,
|
||||||
const kandangsUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
options: kandangOptions,
|
||||||
search: kandangSelectInputValue ?? '',
|
isLoadingOptions: isLoadingKandangOptions,
|
||||||
}).toString()}`;
|
loadMore: loadMoreKandangs,
|
||||||
|
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
|
||||||
const { data: kandangs, isLoading: isLoadingKandangs } = useSWR(
|
|
||||||
kandangsUrl,
|
|
||||||
KandangApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const kandangOptions = isResponseSuccess(kandangs)
|
|
||||||
? kandangs?.data.map((kandang) => ({
|
|
||||||
value: kandang.id,
|
|
||||||
label: kandang.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const typeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const typeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('type', true);
|
formik.setFieldTouched('type', true);
|
||||||
@@ -393,7 +365,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
|||||||
onChange={areaChangeHandler}
|
onChange={areaChangeHandler}
|
||||||
options={areaOptions}
|
options={areaOptions}
|
||||||
onInputChange={setAreaSelectInputValue}
|
onInputChange={setAreaSelectInputValue}
|
||||||
isLoading={isLoadingAreas}
|
onMenuScrollToBottom={loadMoreAreas}
|
||||||
|
isLoading={isLoadingAreaOptions}
|
||||||
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
||||||
errorMessage={formik.errors.areaId as string}
|
errorMessage={formik.errors.areaId as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
@@ -409,7 +382,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
|||||||
onChange={locationChangeHandler}
|
onChange={locationChangeHandler}
|
||||||
options={locationOptions}
|
options={locationOptions}
|
||||||
onInputChange={setLocationSelectInputValue}
|
onInputChange={setLocationSelectInputValue}
|
||||||
isLoading={isLoadingLocations}
|
onMenuScrollToBottom={loadMoreLocations}
|
||||||
|
isLoading={isLoadingLocationOptions}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.locationId && Boolean(formik.errors.locationId)
|
formik.touched.locationId && Boolean(formik.errors.locationId)
|
||||||
}
|
}
|
||||||
@@ -427,7 +401,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
|||||||
onChange={kandangChangeHandler}
|
onChange={kandangChangeHandler}
|
||||||
options={kandangOptions}
|
options={kandangOptions}
|
||||||
onInputChange={setKandangSelectInputValue}
|
onInputChange={setKandangSelectInputValue}
|
||||||
isLoading={isLoadingKandangs}
|
onMenuScrollToBottom={loadMoreKandangs}
|
||||||
|
isLoading={isLoadingKandangOptions}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.kandangId && Boolean(formik.errors.kandangId)
|
formik.touched.kandangId && Boolean(formik.errors.kandangId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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