From 50e881ac6d75d99d7fe8b213297c9e6ee91d9bf2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 21 Feb 2025 02:18:32 +0100 Subject: [PATCH] new ItemSelector based on CounterpartSelector --- .../CreateReceivable/CreateReceivables.tsx | 14 +- .../sections/ItemSelector.tsx | 345 ++++++++++++++++++ .../sections/ItemsSection.tsx | 205 +++++++++-- 3 files changed, 528 insertions(+), 36 deletions(-) create mode 100644 packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/ItemSelector.tsx diff --git a/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx b/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx index 4229ae6b7..cc808ea6c 100644 --- a/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx +++ b/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx @@ -342,19 +342,7 @@ const CreateReceivablesBase = ({ background: 'linear-gradient(180deg, #F6F6F6 0%, #E4E4FF 100%)', height: '100vh', }} - > - - + > >; + setIsEditCounterpartOpened: Dispatch>; + } +); + +//const filter = createFilterOptions(); + +const CREATE_NEW_ID = '__create-new__'; +const DIVIDER = '__divider__'; + +function isCreateNewItemOption(itemOption: any): boolean { + return itemOption?.id === CREATE_NEW_ID; +} + +function isDividerOption( + itemOption: CounterpartsAutocompleteOptionProps | undefined | null +): boolean { + return itemOption?.id === DIVIDER; +} + +export const ItemSelector = ({ + setIsCreateItemOpened, + //setIsEditCounterpartOpened, + isSimplified = false, + disabled, + index = 0, + actualCurrency = 'EUR', + defaultCurrency = 'EUR', +}: any) => { + const { i18n } = useLingui(); + + const { root } = useRootElements(); + const { control, watch } = useForm({ + resolver: yupResolver(getCreateInvoiceProductsValidationSchema(i18n)), + defaultValues: useMemo( + () => ({ + items: [], + currency: actualCurrency ?? defaultCurrency, + }), + [actualCurrency, defaultCurrency] + ), + }); + + const { api } = useMoniteContext(); + const { formatCurrencyToDisplay } = useCurrencies(); + const currency = watch('currency'); + const { + data: productsInfinity, + isLoading, + fetchNextPage, + hasNextPage, + } = api.products.getProducts.useInfiniteQuery( + { + query: { + limit: 20, + currency, + // type: currentFilter[FILTER_TYPE_TYPE] || undefined, + // name__icontains: currentFilter[FILTER_TYPE_SEARCH] || undefined, + }, + }, + { + initialPageParam: { + query: { + pagination_token: undefined, + }, + }, + getNextPageParam: (lastPage) => { + if (!lastPage.next_pagination_token) return; + return { + query: { + pagination_token: lastPage.next_pagination_token, + }, + }; + }, + enabled: !!currency, + } + ); + + const flattenProducts = useMemo( + () => + productsInfinity + ? productsInfinity.pages.flatMap((page) => page.data) + : [], + [productsInfinity] + ); + + const { data: measureUnits } = api.measureUnits.getMeasureUnits.useQuery(); + + const itemsAutocompleteData = useMemo< + CounterpartsAutocompleteOptionProps[] + >(() => { + if (!flattenProducts || flattenProducts.length === 0) { + return []; + } + + return flattenProducts.map((item) => { + const unit = measureUnits + ? measureUnits.data.find((u) => u.id === item.measure_unit_id) + : undefined; + + return { + id: item.id, + label: item.name, + price: item.price, + smallestAmount: item.smallest_amount, + measureUnit: unit, + }; + }); + }, [flattenProducts, measureUnits]); + + const handleCreateNewItem = useCallback(() => { + if (!isSimplified && setIsCreateItemOpened) { + setIsCreateItemOpened(true); + } + }, [isSimplified, setIsCreateItemOpened]); + + const [isFocused, setIsFocused] = useState(false); + + return !flattenProducts || flattenProducts.length === 0 ? null : ( + { + const selectedItem = flattenProducts?.find( + (item) => item.id === field.value + ); + + /** + * We have to set `selectedCounterpartOption` to `null` + * if `selectedCounterpart` is `null` because + * `Autocomplete` component doesn't work with `undefined` + */ + const selectedItemOption = selectedItem + ? { + id: selectedItem.id, + label: selectedItem.name, + } + : null; + return ( + <> + { + if (isCreateNewItemOption(value) || isDividerOption(value)) { + field.onChange(null); + + return; + } + field.onChange(value?.id); + }} + slotProps={{ + popper: { + container: root, + }, + }} + renderInput={(params) => { + return ( + setIsFocused(true), + onBlur: () => setIsFocused(false), + startAdornment: isLoading ? ( + + ) : ( + !isSimplified && ( + <> + {!isFocused && ( + + + {params.inputProps.value} + + + )} + + ) + ), + endAdornment: (() => { + if ( + isSimplified && + !params.inputProps['aria-expanded'] + ) { + return ; + } + if ( + selectedItemOption && + params.inputProps['aria-expanded'] + ) { + return ( + field.onChange(null)}> + + + ); + } + return null; + })(), + }} + /> + ); + }} + loading={isLoading || disabled} + options={itemsAutocompleteData} + getOptionLabel={(itemOption) => + isCreateNewItemOption(itemOption) || isDividerOption(itemOption) + ? '' + : itemOption.label + } + isOptionEqualToValue={(option, value) => { + return option.id === value.id; + }} + selectOnFocus + clearOnBlur + handleHomeEndKeys + renderOption={( + props, + itemOption: CounterpartsAutocompleteOptionProps + ) => + isCreateNewItemOption(itemOption) ? ( + + ) : itemOption.id === DIVIDER ? ( + + ) : ( +
  • + {itemOption.label} + + {itemOption.smallestAmount}{' '} + {itemOption.measureUnit.description} /{' '} + {itemOption.price && + formatCurrencyToDisplay( + itemOption.price.value, + itemOption.price.currency + )} + +
  • + ) + } + /> + + ); + }} + /> + ); +}; diff --git a/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/ItemsSection.tsx b/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/ItemsSection.tsx index 084afb169..a0040b77e 100644 --- a/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/ItemsSection.tsx +++ b/packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/ItemsSection.tsx @@ -46,6 +46,8 @@ import { } from '@mui/material'; import { styled } from '@mui/material'; +import { ItemSelector } from './ItemSelector'; + interface VatRateControllerProps { index: number; vatRates?: VatRateListResponse; @@ -226,6 +228,7 @@ export const ItemsSection = ({ control, name: 'line_items', }); + const rows = 2; const watchedLineItems = watch('line_items'); const { api } = useMoniteContext(); const { data: vatRates } = api.vatRates.getVatRates.useQuery(); @@ -244,6 +247,7 @@ export const ItemsSection = ({ const handleCloseProductsTable = useCallback(() => { setProductsTableOpen(false); }, []); + console.log({ watchedLineItems }); const { subtotalPrice, @@ -285,6 +289,8 @@ export const ItemsSection = ({ vatRates?.data[0] ); + const { getSymbolFromCurrency } = useCurrencies(); + const StyledTableCell = styled(TableCell)` max-width: 100px; `; @@ -334,9 +340,11 @@ export const ItemsSection = ({ - {fields.map((field, index) => ( - - {field.name} + {Array.from({ length: rows }).map((_, index) => ( + + + + + {`line_items.${index}.measure_unit_id` ? ( + + ) : ( + 'notloaded' + )} )} /> - {field.measure_unit_id ? ( - - ) : ( - '—' - )} - {field.price && - formatCurrencyToDisplay( - field.price.value, - field.price.currency - )} { + const formattedValue = formatCurrencyToDisplay( + controllerField.value?.value || 0, + controllerField.value?.currency || 'USD', + false + ); + return ( + + { + const newValue = parseFloat( + e.target.value.replace(/[^0-9.]/g, '') + ); + if (!isNaN(newValue)) { + controllerField.onChange({ + ...controllerField.value, + value: newValue, + }); + } + }} + /> + + ); + }} + /> + + + {isNonVatSupported ? ( + ( + + + + )} + /> + ) : ( + + )} + + + + { + remove(index); + }} + > + + + + + ))} + {fields.map((field, index) => ( + + {field.name} + + ( - + )} /> + {field.measure_unit_id ? ( + + ) : ( + '—' + )} + + + { + const formattedValue = formatCurrencyToDisplay( + controllerField.value?.value || 0, + controllerField.value?.currency || 'USD', + false + ); + return ( + + { + const newValue = parseFloat( + e.target.value.replace(/[^0-9.]/g, '') + ); + if (!isNaN(newValue)) { + controllerField.onChange({ + ...controllerField.value, + value: newValue, + }); + } + }} + /> + + ); + }} + /> {isNonVatSupported ? (